Skip to content

feat(broadcast): add unbounded policy - #117

Open
orthur2 wants to merge 4 commits into
apache:mainfrom
orthur2:feat/broadcast-unbounded
Open

feat(broadcast): add unbounded policy#117
orthur2 wants to merge 4 commits into
apache:mainfrom
orthur2:feat/broadcast-unbounded

Conversation

@orthur2

@orthur2 orthur2 commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add broadcast::unbounded, a lossless broadcast policy that retains each message until every active receiver has consumed it or has been dropped
  • keep send synchronous and use receive errors without a Lagged variant
  • make a cloned receiver start at the original receiver's current cursor, while subscribe and resubscribe start at the current tail
  • expose Sender::buffer_len, Sender::receiver_count, Receiver::len, and Receiver::is_empty
  • document the policy in the crate API guide, README, and changelog
  • add integration coverage and a dedicated benchmark group

Implementation

The buffer, receiver cursors, and receive waiters are protected by the same mutex. send appends the message and takes the parked wakers while holding that lock. Recv::poll likewise 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 uses Arc::try_unwrap to 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::clone increments the sender count with Relaxed: 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 by send when 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_receivers records 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 1 had a median reclaim cost of 124.4 ns, compared with 49.62 ns for peak 1 / live 1. The benchmark keeps that cost visible for future work on the cursor scan.

Validation

  • cargo x check
  • cargo x test --no-capture
  • cargo x bench
  • cargo +nightly clippy --tests --all-features --all-targets --workspace -- -D warnings
  • cargo +nightly fmt --all --check

Refs #167. Related to #143 and #145.

@tisonkun
tisonkun force-pushed the feat/broadcast-unbounded branch from db690ce to 40f4e6c Compare August 21, 2026 01:47
@orthur2
orthur2 marked this pull request as ready for review August 22, 2026 15:29
@orthur2
orthur2 force-pushed the feat/broadcast-unbounded branch from 54355f8 to a116888 Compare August 22, 2026 15:30
@orthur2
orthur2 force-pushed the feat/broadcast-unbounded branch from a116888 to 8cbea5b Compare August 22, 2026 15:34
@orthur2

orthur2 commented Aug 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @tisonkun for introducing Arena in #143 , and for moving this branch from Slab to Arena and adapting it to the current WaitRegistration APIs. And I've pushed two follow-up commits to refine the implementation.

When you have a chance, could you take another look and let me know what you think?

@tisonkun
tisonkun self-requested a review August 22, 2026 16:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant