Skip to main content

dtact_util/sync/
broadcast.rs

1//! Multi-producer, multi-consumer broadcast channel.
2//!
3//! Pre-allocated, zero-allocation on the send path, and cache-friendly.
4//! Eliminates hazard pointer linear scans entirely.
5
6use super::wait_queue::WaitQueue;
7use std::cell::UnsafeCell;
8use std::mem::MaybeUninit;
9use std::sync::Arc;
10use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
11use std::task::{Context, Poll};
12
13/// A single pre-allocated slot inside the contiguous ring buffer.
14#[repr(align(64))]
15struct Slot<T> {
16    /// Tracks the absolute message sequence number.
17    /// Bit 63 can be used as a "writing/locked" flag, or we can just rely on
18    /// updating it after writing the value.
19    seq: AtomicU64,
20    /// The value is stored completely inline. Zero heap allocations.
21    value: UnsafeCell<MaybeUninit<T>>,
22}
23
24unsafe impl<T: Send> Send for Slot<T> {}
25unsafe impl<T: Sync> Sync for Slot<T> {}
26
27impl<T> Slot<T> {
28    const fn new() -> Self {
29        Self {
30            seq: AtomicU64::new(u64::MAX),
31            value: UnsafeCell::new(MaybeUninit::uninit()),
32        }
33    }
34}
35
36#[repr(align(64))]
37struct Shared<T> {
38    /// Contiguous, pre-allocated ring buffer. Zero heap allocations on the hot path.
39    slots: Box<[Slot<T>]>,
40    capacity: usize,
41    next_seq: AtomicU64,
42    sender_count: AtomicUsize,
43    wait: WaitQueue,
44}
45
46unsafe impl<T: Send> Send for Shared<T> {}
47unsafe impl<T: Sync> Sync for Shared<T> {}
48
49/// Create a broadcast channel with a `capacity`-entry backlog.
50#[must_use]
51#[inline]
52pub fn channel<T: Clone>(capacity: usize) -> (Sender<T>, Receiver<T>) {
53    let capacity = capacity.max(1);
54
55    let mut slots = Vec::with_capacity(capacity);
56    for _ in 0..capacity {
57        slots.push(Slot::new());
58    }
59
60    let shared = Arc::new(Shared {
61        slots: slots.into_boxed_slice(),
62        capacity,
63        next_seq: AtomicU64::new(0),
64        sender_count: AtomicUsize::new(1),
65        wait: WaitQueue::new(),
66    });
67
68    let receiver = Receiver {
69        shared: shared.clone(),
70        next_seq: 0,
71    };
72
73    (Sender { shared }, receiver)
74}
75
76/// The sending half of a [`channel`]. Cheaply [`Clone`]-able.
77#[repr(align(64))]
78pub struct Sender<T> {
79    shared: Arc<Shared<T>>,
80}
81
82impl<T> Clone for Sender<T> {
83    #[inline(always)]
84    fn clone(&self) -> Self {
85        self.shared.sender_count.fetch_add(1, Ordering::AcqRel);
86        Self {
87            shared: self.shared.clone(),
88        }
89    }
90}
91
92impl<T> Drop for Sender<T> {
93    #[inline(always)]
94    fn drop(&mut self) {
95        if self.shared.sender_count.fetch_sub(1, Ordering::AcqRel) == 1 {
96            self.shared.wait.wake_all();
97        }
98    }
99}
100
101impl<T: Clone> Sender<T> {
102    /// Broadcast `value` to every current and future receiver.
103    ///
104    /// Zero heap allocations. Purely atomic array coordination.
105    ///
106    /// # Errors
107    ///
108    /// Returns `SendError` if all downstream receivers have been dropped,
109    /// leaving nobody left to consume the payload.
110    #[inline(always)]
111    pub fn send(&self, value: T) -> Result<(), SendError<T>> {
112        if self.receiver_count() == 0 {
113            return Err(SendError(value));
114        }
115
116        let seq = self.shared.next_seq.fetch_add(1, Ordering::Relaxed);
117        let idx = (seq as usize) % self.shared.capacity;
118        let slot = &self.shared.slots[idx];
119
120        // 1. Mark the slot as transitioning/locked by storing an intermediate flag
121        // or letting readers know it's being overwritten.
122        // Storing u64::MAX - 1 flags a transient state.
123        slot.seq.store(u64::MAX - 1, Ordering::Release);
124
125        // 2. Write/Overwrite the element directly in place (Zero-alloc)
126        unsafe {
127            let ptr = slot.value.get();
128            // If the channel has wrapped around, we need to drop the old value inline safely
129            if seq >= self.shared.capacity as u64 {
130                std::ptr::drop_in_place((*ptr).as_mut_ptr());
131            }
132            std::ptr::write((*ptr).as_mut_ptr(), value);
133        }
134
135        // 3. Publish the final absolute sequence number. Unlocks for readers.
136        slot.seq.store(seq, Ordering::Release);
137
138        if self.shared.wait.has_waiters() {
139            self.shared.wait.wake_all();
140        }
141
142        Ok(())
143    }
144
145    /// Current number of live receivers (a lower bound under concurrent
146    /// clone/drop, same caveat `tokio`'s equivalent has).
147    #[must_use]
148    #[inline(always)]
149    pub fn receiver_count(&self) -> usize {
150        Arc::strong_count(&self.shared)
151            .saturating_sub(self.shared.sender_count.load(Ordering::Acquire))
152    }
153}
154
155/// The receiving half of a [`channel`].
156#[repr(align(64))]
157pub struct Receiver<T> {
158    shared: Arc<Shared<T>>,
159    next_seq: u64,
160}
161
162#[allow(clippy::non_send_fields_in_send_ty)]
163unsafe impl<T: Send> Send for Receiver<T> {}
164unsafe impl<T: Send> Sync for Receiver<T> {}
165
166impl<T> Clone for Receiver<T> {
167    #[inline(always)]
168    fn clone(&self) -> Self {
169        Self {
170            shared: self.shared.clone(),
171            next_seq: self.next_seq,
172        }
173    }
174}
175
176impl<T: Clone> Receiver<T> {
177    /// Receive the next value, waiting if none is available yet.
178    ///
179    /// # Errors
180    /// Returns [`RecvError::Lagged`] (and advances past the gap) if this
181    /// receiver fell behind the buffer's `capacity` since its last
182    /// `recv()`, or [`RecvError::Closed`] once every [`Sender`] has been
183    /// dropped and the backlog is drained.
184    #[inline(always)]
185    pub async fn recv(&mut self) -> Result<T, RecvError> {
186        std::future::poll_fn(|cx| self.poll_recv(cx)).await
187    }
188
189    #[inline]
190    fn poll_recv(&mut self, cx: &Context<'_>) -> Poll<Result<T, RecvError>> {
191        if let Some(result) = self.try_recv_one() {
192            return Poll::Ready(result);
193        }
194        if self.is_closed() {
195            return Poll::Ready(self.try_recv_one().unwrap_or(Err(RecvError::Closed)));
196        }
197        let token = self.shared.wait.register(cx.waker());
198        if let Some(result) = self.try_recv_one() {
199            self.shared.wait.cancel(token);
200            return Poll::Ready(result);
201        }
202        if self.is_closed() {
203            let result = self.try_recv_one().unwrap_or(Err(RecvError::Closed));
204            self.shared.wait.cancel(token);
205            return Poll::Ready(result);
206        }
207        Poll::Pending
208    }
209
210    #[inline]
211    fn try_recv_one(&mut self) -> Option<Result<T, RecvError>> {
212        let idx = (self.next_seq as usize) % self.shared.capacity;
213        let slot = &self.shared.slots[idx];
214
215        let slot_seq = slot.seq.load(Ordering::Acquire);
216
217        // If the slot is uninitialized or currently being written to, bail immediately.
218        // No tight spin loops! Let the async runtime handle re-polling.
219        if slot_seq == u64::MAX || slot_seq == u64::MAX - 1 {
220            return None;
221        }
222
223        match self.next_seq.cmp(&slot_seq) {
224            std::cmp::Ordering::Equal => {
225                // SAFETY: slot_seq matches exactly what we expect. Clone inline data.
226                let cloned_val = unsafe {
227                    let ptr = slot.value.get();
228                    (*ptr).assume_init_ref().clone()
229                };
230
231                // Double check sequence to ensure a blazing fast sender didn't
232                // wrap around and overwrite us mid-clone.
233                if slot.seq.load(Ordering::Acquire) != slot_seq {
234                    return Some(Err(RecvError::Lagged(1)));
235                }
236
237                self.next_seq += 1;
238                Some(Ok(cloned_val))
239            }
240            std::cmp::Ordering::Less => {
241                let skipped = slot_seq - self.next_seq;
242                self.next_seq = slot_seq;
243                Some(Err(RecvError::Lagged(skipped)))
244            }
245            std::cmp::Ordering::Greater => {
246                None // Sender hasn't caught up to this slot index yet
247            }
248        }
249    }
250
251    #[inline(always)]
252    fn is_closed(&self) -> bool {
253        self.shared.sender_count.load(Ordering::Acquire) == 0
254    }
255}
256
257impl<T> Drop for Shared<T> {
258    #[inline(always)]
259    fn drop(&mut self) {
260        let next_seq = self.next_seq.load(Ordering::Acquire);
261        // Only drop slots that were actually written to
262        let initialized_elements = next_seq.min(self.capacity as u64) as usize;
263        for i in 0..initialized_elements {
264            unsafe {
265                let ptr = self.slots[i].value.get();
266                std::ptr::drop_in_place((*ptr).as_mut_ptr());
267            }
268        }
269    }
270}
271
272/// Error returned by [`Sender::send`] when there are no receivers to
273/// deliver to. Carries the value back to the caller.
274#[derive(Debug, Clone, Copy, PartialEq, Eq)]
275#[repr(align(64))]
276pub struct SendError<T>(pub T);
277
278impl<T> std::fmt::Display for SendError<T> {
279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280        f.write_str("broadcast channel has no receivers")
281    }
282}
283
284impl<T: std::fmt::Debug> std::error::Error for SendError<T> {}
285
286/// Error returned by [`Receiver::recv`].
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288#[repr(align(64))]
289pub enum RecvError {
290    /// This receiver fell behind by the contained number of messages;
291    /// they were overwritten before it could read them. The receiver has
292    /// been fast-forwarded past the gap and will resume from the oldest
293    /// still-buffered message on the next `recv()`.
294    Lagged(u64),
295    /// Every [`Sender`] has been dropped and the backlog is drained —
296    /// no further values will ever arrive.
297    Closed,
298}
299
300impl std::fmt::Display for RecvError {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        match self {
303            Self::Lagged(n) => write!(f, "receiver lagged behind by {n} messages"),
304            Self::Closed => f.write_str("channel closed: every sender dropped"),
305        }
306    }
307}
308
309impl std::error::Error for RecvError {}