Skip to main content

proxima_primitives/sync/
broadcast.rs

1//! `proxima::sync::broadcast` — multi-producer multi-consumer
2//! broadcast channel, shape-compatible with `tokio::sync::broadcast`.
3//! Backed by `async_broadcast` configured for overflow semantics:
4//! producers never block; if the buffer fills, the oldest message is
5//! dropped and the affected receivers see `RecvError::Lagged(skipped)`
6//! on their next `recv`.
7//!
8//! Differences from tokio:
9//! - `error::RecvError::Lagged(u64)` is named to match tokio's
10//!   discriminant. (Underlying crate calls it `Overflowed`.)
11//! - `send` returns `Ok(subscriber_count)` matching tokio's signature.
12//!
13//! # Non-coverage
14//!
15//! - `Sender::send` returns `Ok(subscriber_count)` but the count is
16//!   sampled at send time and is eventually-consistent with concurrent
17//!   subscribe/drop operations. tokio's version has the same property
18//!   but we call it out here because a sharp caller may rely on the
19//!   exact-snapshot semantics that neither implementation provides.
20//! - `Receiver::resubscribe() -> Receiver<T>` — fresh subscription
21//!   anchored at the current end of stream. Not exposed today; if
22//!   needed, clone the `Sender` and call `subscribe()`.
23//! - `Receiver::len()` / `Receiver::is_empty()` — receiver-local
24//!   buffer introspection. `async-broadcast` has equivalents but no
25//!   internal caller plumbs them.
26//! - `Receiver::same_channel(&other)` — identity check.
27//! - `Sender::receiver_count()` IS exposed but the value is a
28//!   snapshot; we expose it because `recording/broadcast.rs` uses it
29//!   to decide whether to bother broadcasting (best-effort).
30//! - `Sender::weak_count()` and weak-sender plumbing — no caller.
31
32use async_broadcast::{InactiveReceiver, Sender as InnerSender};
33
34pub mod error {
35    /// Error returned from [`super::Receiver::recv`] when the channel
36    /// is no longer producing values, or when this receiver skipped
37    /// past some messages because it was too slow.
38    #[derive(Debug, thiserror::Error)]
39    pub enum RecvError {
40        /// All senders have been dropped and the buffer is drained.
41        #[error("broadcast channel closed")]
42        Closed,
43        /// The buffer overflowed and this receiver skipped `n` messages.
44        /// The next `recv` will return the next available value.
45        #[error("broadcast receiver lagged behind by {0} messages")]
46        Lagged(u64),
47    }
48}
49
50/// Error returned from [`Sender::send`] when no receivers are
51/// listening. The value is returned so callers can retry on a fresh
52/// channel.
53#[derive(Debug, thiserror::Error)]
54#[error("broadcast channel has no receivers")]
55pub struct SendError<T>(pub T);
56
57/// Producer side. Cheaply cloneable.
58#[derive(Debug)]
59pub struct Sender<T: Clone> {
60    inner: InnerSender<T>,
61    /// Kept alive so the channel stays open while at least one Sender
62    /// exists, even if every Receiver has been dropped. Mirrors
63    /// tokio's "channel stays open until all Senders drop" rule.
64    _liveness: InactiveReceiver<T>,
65}
66
67impl<T: Clone> Clone for Sender<T> {
68    fn clone(&self) -> Self {
69        Self {
70            inner: self.inner.clone(),
71            _liveness: self._liveness.clone(),
72        }
73    }
74}
75
76impl<T: Clone> Sender<T> {
77    /// Send a value to every active receiver. Returns the subscriber
78    /// count at the time of the send. Returns `SendError(value)` if
79    /// no receivers are listening.
80    pub fn send(&self, value: T) -> Result<usize, SendError<T>> {
81        if self.inner.receiver_count() == 0 {
82            return Err(SendError(value));
83        }
84        match self.inner.try_broadcast(value) {
85            Ok(_displaced) => Ok(self.inner.receiver_count()),
86            Err(async_broadcast::TrySendError::Closed(value)) => Err(SendError(value)),
87            Err(async_broadcast::TrySendError::Inactive(value)) => Err(SendError(value)),
88            Err(async_broadcast::TrySendError::Full(value)) => {
89                // overflow mode is set in `channel()`, so Full should
90                // not occur; treat as logic error returning value back.
91                Err(SendError(value))
92            }
93        }
94    }
95
96    /// Build a fresh receiver subscribed at the current end of the
97    /// stream. Messages broadcast BEFORE this call are not delivered
98    /// to the new receiver.
99    pub fn subscribe(&self) -> Receiver<T> {
100        Receiver {
101            inner: self.inner.new_receiver(),
102        }
103    }
104
105    /// Returns the number of currently-active receivers.
106    pub fn receiver_count(&self) -> usize {
107        self.inner.receiver_count()
108    }
109}
110
111/// Consumer side. Each receiver has its own cursor across the buffer.
112#[derive(Debug)]
113pub struct Receiver<T: Clone> {
114    inner: async_broadcast::Receiver<T>,
115}
116
117impl<T: Clone> Receiver<T> {
118    /// Await the next message. Returns `Closed` when all senders are
119    /// dropped and the buffer is drained; `Lagged(n)` if the receiver
120    /// skipped `n` messages due to buffer overflow.
121    pub async fn recv(&mut self) -> Result<T, error::RecvError> {
122        match self.inner.recv().await {
123            Ok(value) => Ok(value),
124            Err(async_broadcast::RecvError::Closed) => Err(error::RecvError::Closed),
125            Err(async_broadcast::RecvError::Overflowed(skipped)) => {
126                Err(error::RecvError::Lagged(skipped))
127            }
128        }
129    }
130}
131
132impl<T: Clone> Clone for Receiver<T> {
133    fn clone(&self) -> Self {
134        Self {
135            inner: self.inner.clone(),
136        }
137    }
138}
139
140/// Build a broadcast channel with `capacity` slots. Producers never
141/// block on a full buffer; the oldest message is dropped and the
142/// affected receivers observe `Lagged` on their next `recv`.
143pub fn channel<T: Clone>(capacity: usize) -> (Sender<T>, Receiver<T>) {
144    let (mut sender, receiver) = async_broadcast::broadcast(capacity.max(1));
145    sender.set_overflow(true);
146    sender.set_await_active(false);
147    let liveness = receiver.clone().deactivate();
148    (
149        Sender {
150            inner: sender,
151            _liveness: liveness,
152        },
153        Receiver { inner: receiver },
154    )
155}
156
157// under `--cfg loom`, `async_broadcast` becomes loom-instrumented too (it
158// depends on `event_listener`) and requires every call inside a
159// `loom::model()`; these plain tests are unrelated to the Notify/watch
160// loom protocol this crate loom-tests, so they're normal (non-loom)
161// build only.
162#[cfg(all(test, not(loom)))]
163#[allow(clippy::unwrap_used, clippy::expect_used)]
164mod tests {
165    use super::*;
166    use futures::executor::block_on;
167
168    #[test]
169    fn send_to_active_receiver_roundtrips() {
170        let (sender, mut receiver) = channel::<u32>(4);
171        block_on(async move {
172            sender.send(7).expect("send");
173            assert_eq!(receiver.recv().await.expect("recv"), 7);
174        });
175    }
176
177    #[test]
178    fn second_subscriber_only_sees_messages_sent_after_subscribe() {
179        let (sender, mut alpha) = channel::<u32>(4);
180        sender.send(1).expect("send 1");
181        let mut beta = sender.subscribe();
182        sender.send(2).expect("send 2");
183        block_on(async move {
184            assert_eq!(alpha.recv().await.expect("alpha first"), 1);
185            assert_eq!(alpha.recv().await.expect("alpha second"), 2);
186            assert_eq!(beta.recv().await.expect("beta"), 2);
187        });
188    }
189
190    #[test]
191    fn send_succeeds_when_no_receivers_but_inactive_holds_open() {
192        let (sender, receiver) = channel::<u32>(4);
193        drop(receiver);
194        // No active receivers; send returns SendError so callers can recover.
195        match sender.send(1) {
196            Err(SendError(value)) => assert_eq!(value, 1),
197            Ok(_) => panic!("expected SendError"),
198        }
199    }
200
201    #[test]
202    fn overflow_yields_lagged_on_recv() {
203        let (sender, mut receiver) = channel::<u32>(2);
204        sender.send(1).expect("send 1");
205        sender.send(2).expect("send 2");
206        sender.send(3).expect("send 3 (overflows 1)");
207        block_on(async move {
208            match receiver.recv().await {
209                Err(error::RecvError::Lagged(skipped)) => assert!(skipped >= 1),
210                other => panic!("expected Lagged, got {other:?}"),
211            }
212        });
213    }
214}