-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sync: add broadcast channel #1943
Conversation
Adds a broadcast channel implementation. A broadcast channel is a multi-producer, multi-consumer channel where each consumer receives a clone of every value sent. This is useful for implementing pub / sub style patterns. Implemented as a ring buffer, a Vec of the specified capacity is allocated on initialization of the channel. Values are pushed into slots. When the channel is full, a send overwrites the oldest value. Receivers detect this and return an error on the next call to receive. This prevents unbounded buffering and does not make the channel vulnerable to the slowest consumer. Closes: #1585
One question is if |
+1 on the bounded implementation! That part it the hardest for any "broadcast" like implementation. API-wise you could cross-check against futures-intrusive::channel::StateBroadcastChannel, which is pretty much the same thing - but with a channel size preconfigured to 1, so it will only retain the latest element. This is the "latest state", which consumers are typically interested in. Actually it could be made configurable there too. Anyway, what
|
cc @habnabit |
`send` now returns the number of receivers subscribed at the time `send` is called. The `Lagged` error now includes the number of skipped messages.
I added the number of skipped messages to the I also return from |
Co-Authored-By: Eliza Weisman <[email protected]>
Co-Authored-By: Eliza Weisman <[email protected]>
if tail.rx_cnt == MAX_RECEIVERS { | ||
panic!("max receivers"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit/tioli: could be
assert!(tail.rx_cnt != MAX_RECEIVERS, "max receivers");
I've been trying this out in a demo application of mine (commit at https://git.jebrosen.com/jeb/rocket-rooms/commit/e3fa0b106566a55a9bbcb82854b6be71cd254fff) and I really like it so far. It's as good or better than my naive wrapper over |
@jebrosen thanks for giving it a run, I tried it a bit locally and also works for me. I'm happy merging this if others are 👍 |
@LucioFranco @jonhoo @hawkw i need a +1 on this to merge :) |
tokio/src/sync/broadcast.rs
Outdated
|
||
/// Receiving-half of the [`broadcast`] channel. | ||
/// | ||
/// May not be used concurrently. Messages may be retrieved using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// May not be used concurrently. Messages may be retrieved using | |
/// Should not be used concurrently. Messages may be retrieved using |
I think this sounds a bit better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"should not" seems like a suggestion and "may not" is prohibited?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah then you want Must not
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't ask my why I know this is here but https://www.faa.gov/air_traffic/publications/atpubs/atc_html/chap1_section_2.html is a good reference :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally find all of these wordings confusing. "May not / must not / should not" reads like something I have to keep in mind when I use the API otherwise some bad thing will happen, but the only consequence I can see of using this concurrently is that rustc
will complain because all of the (public) methods take &mut self
. std::sync::mpsc::Receiver
uses the wording "This half can only be owned by one thread" which I'm also not quite happy with but IMO is less open to confusion.
/// assert_eq!(20, value); | ||
/// } | ||
/// ``` | ||
pub fn subscribe(&self) -> Receiver<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there value in having this versus the way we normally do it with a mpsc
? May make sense to align the apis
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Subscribing starts at the head of the list. A receiver may currently be pointing to any index. A clone()
would then not return an "identical" item. I thought it didn't match.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, Im not gonna reread the docs but can we make sure this is documented somewhere. This point is totally fair.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we maybe add a method to Reciever
that can get you one that starts at the head?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would opt to punt on that until there is a clear case where that is helpful. I'm not sure what it would be named or if it makes sense 🤷
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You know me I like to punt 👍
/// internal cursor is updated to point to the oldest value still held by | ||
/// the channel. A subsequent call to [`try_recv`] will return this value | ||
/// **unless** it has been since overwritten. | ||
/// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing doc on TryRecvError::Empty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At a high level this LGTM, hopping loom covers most of the cases 😄
Adds a broadcast channel implementation. A broadcast channel is a
multi-producer, multi-consumer channel where each consumer receives a
clone of every value sent. This is useful for implementing pub / sub
style patterns.
Implemented as a ring buffer, a Vec of the specified capacity is
allocated on initialization of the channel. Values are pushed into
slots.
When the channel is full, a send overwrites the oldest value. Receivers
detect this and return an error on the next call to receive. This
prevents unbounded buffering and does not make the channel vulnerable to
the slowest consumer.
This is ready for review, but should not be merged until it has some
application validation.
Closes: #1585