feat(broadcast): add a bounded MPMC broadcast channel - #253
Conversation
Benchmark results40 runs per configuration; the ratio is taken within each run so drift cancels, then averaged.
Not bounded-specific. Same method on the existing unbounded comparison — same direction, and ahead of
So the cost sits in the retention machinery #117 shipped and #213 asks this PR to reuse: one A rejected optimisation.
Net-neutral at best, so Closing the two bad regimes means revisiting the |
Add `broadcast::mpmc::bounded`, a lossless bounded broadcast channel. Every accepted value stays readable by every subscription that was active when it was accepted, so a receive never reports lag. Capacity counts the shared backlog held by the slowest active subscription: `send` waits on it and `try_send` reports `TrySendError::Full` without taking the value. Nothing slides, overwrites, or is dropped to make room. Move the backlog, the cursors, and the one receive poll step both families share into a private `common` module, while each keeps its own publish path and `Recv` future so neither retention contract hides behind a shared abstraction. Ring, cursor, retention, and sequencing types stay private to `broadcast::mpmc`. Signed-off-by: onenewcode <lovestudy@qq.com>
1d2bbb7 to
4ffb990
Compare
Closes #213.
Adds
broadcast::mpmc::bounded, the bounded half of the lossless MPMC broadcast pair. The unbounded half shipped in #117; this reuses its retention machinery without blurring either contract.Contract
sendwaits until the slowest subscription consumes a retained message or is dropped;try_sendreportsTrySendError::Fulland hands the value back untouched.tailadvances and the message lands in the buffer inside the same critical section, so with several producers a later claim can never surface ahead of an earlier one, and every subscription observes the identical sequence.bounded(0)panics.Structure
The backlog, the cursors, and the one receive poll step whose waker protocol is subtle enough to be worth a single copy move into a private
commonmodule. Each family keeps its own publish path and its ownRecvfuture, so neither retention contract hides behind a shared abstraction. Ring, cursor, retention, and sequencing types stay private tobroadcast::mpmc; nothing new was promoted tocrate::internalbeyond enabling the existing semaphore and wait set for thebroadcastfeature.Payloads are held behind an
ArcsoT::cloneandT::dropnever run under the channel lock — both are arbitrary user code that may reenter the channel. Producers waiting on capacity park on the internal semaphore, and reclaimed slots are released before the receive touches the payload, so a panickingT::cloneorT::dropcannot strand a producer on capacity it already freed.Tests
27 integration tests plus 5 Miri-sized unit tests