feat(broadcast): add unbounded policy - #117
Open
orthur2 wants to merge 4 commits into
Open
Conversation
tisonkun
force-pushed
the
feat/broadcast-unbounded
branch
from
August 21, 2026 01:47
db690ce to
40f4e6c
Compare
orthur2
marked this pull request as ready for review
August 22, 2026 15:29
orthur2
force-pushed
the
feat/broadcast-unbounded
branch
from
August 22, 2026 15:30
54355f8 to
a116888
Compare
orthur2
force-pushed
the
feat/broadcast-unbounded
branch
from
August 22, 2026 15:34
a116888 to
8cbea5b
Compare
Contributor
Author
|
Thanks @tisonkun for introducing Arena in #143 , and for moving this branch from When you have a chance, could you take another look and let me know what you think? |
tisonkun
self-requested a review
August 22, 2026 16:23
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
broadcast::unbounded, a lossless broadcast policy that retains each message until every active receiver has consumed it or has been droppedsendsynchronous and use receive errors without aLaggedvariantsubscribeandresubscribestart at the current tailSender::buffer_len,Sender::receiver_count,Receiver::len, andReceiver::is_emptyImplementation
The buffer, receiver cursors, and receive waiters are protected by the same mutex.
sendappends the message and takes the parked wakers while holding that lock.Recv::polllikewise decides between receiving a message, reporting disconnection, and registering its waker in one critical section. A receiver therefore cannot observe an empty buffer and park after a message has already been published.Messages are stored as
Arc<T>so cloning and dropping user values happen after the channel is unlocked. When a receive advances the slowest cursor, the reclaimed prefix tells it that the buffer no longer owns the message. It drops the reclaimed references and usesArc::try_unwrapto move the payload out. A single-receiver channel does not clone payloads; cloning remains the fallback when another receiver still holds an in-flight reference.The buffer retains capacity used by a steady fill-and-drain workload. Capacity grown for a one-off burst is released after a later cycle drains without needing it.
Message versions use checked arithmetic rather than wrapping because receiver cursors are absolute offsets into the retained buffer.
Sender::cloneincrements the sender count withRelaxed: the count does not publish messages, and message visibility is synchronized by the channel mutex.Wakers, reclaimed messages, and messages sent after the last receiver is dropped are all released after unlocking the channel.
Tests and benchmarks
Public behavior is covered from
tests-integration; source-local tests are limited to version-counter overflow and the two buffer-capacity cycles.The integration suite covers receiver cursors, slow receivers, cancellation, disconnect ordering, concurrent senders, randomized operations, payload ownership, a panicking
Clone, and reentrant message destructors. The latter also covers the value dropped bysendwhen no receivers remain.The benchmark group covers empty and owned receives, shared payloads, waiter delivery and cancellation, concurrent send-and-drain, fanout, and receiver high-water behavior.
drain_with_receiversrecords peak and live receiver counts separately because reclaiming a prefix scans the receiver arena up to its historical high-water mark. In the latest local macOS arm64 run,peak 256 / live 1had a median reclaim cost of 124.4 ns, compared with 49.62 ns forpeak 1 / live 1. The benchmark keeps that cost visible for future work on the cursor scan.Validation
cargo x checkcargo x test --no-capturecargo x benchcargo +nightly clippy --tests --all-features --all-targets --workspace -- -D warningscargo +nightly fmt --all --checkRefs #167. Related to #143 and #145.