Skip to content

feat(broadcast): return shared messages from recv - #359

Open
orthur2 wants to merge 1 commit into
apache:mainfrom
orthur2:feat/broadcast-shared-recv
Open

orthur2 wants to merge 1 commit into
apache:mainfrom
orthur2:feat/broadcast-shared-recv

Conversation

@orthur2

@orthur2 orthur2 commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #312.

recv and try_recv on both broadcast MPMC receivers now return the channel's own Arc<T>, and bounded and unbounded no longer require T: Clone. A message fanned out to N subscribers is one allocation shared N times, with no T::clone anywhere. Callers that want an owned T use the new recv_cloned and try_recv_cloned, which keep today's behavior of moving the payload out instead of cloning it when the receiver ends up with the only reference.

This is a breaking change. Existing callers either switch to recv_cloned or deref the Arc, and the changelog says so under Breaking changes. cargo x semver does not flag it, because cargo-semver-checks has no lint for a changed return type on an inherent method, so the changelog entry is the only record.

An Arc kept past the receive keeps the payload alive after the message leaves the backlog, so bounded capacity still limits retained messages rather than memory. That was already the case for cloned values.

Design Notes

The channel already stores every message behind an Arc so that cloning and dropping payloads happens outside the lock, and nothing under the lock changes here. The only change is the step after the lock is released. take_msg is split into share_msg, which releases the backlog's reference and hands out the stored Arc, and the existing take_msg, which still decides ownership from the reclaim bookkeeping instead of probing the reference count and now falls back through Arc::unwrap_or_clone. An Arc kept from another subscriber's recv is a new reason for that fallback to clone, and a test checks it clones exactly once in that case.

Each channel's private Recv future now yields the received pair and the four public methods wrap it, so registration, cancellation and the drop path are the same code for recv and recv_cloned. In the bounded channel the capacity release still happens before take_msg can run T::clone, so a panicking clone cannot strand a blocked producer. A test parks a send, panics inside recv_cloned, and checks the producer is woken. The shared path runs no user code at all, since a receive reclaims at most the message itself and the returned Arc keeps it alive.

The ecosystem benchmarks use the cloned variants so the comparison with tokio and async-broadcast, which return owned values, stays fair. broadcast/spmc is untouched because #317 is adding it.

This branch has not been deployed

No deployments
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.

Discuss shared broadcast receives without a Clone bound

1 participant