Skip to main content

rivet/sync/
channel.rs

1//! Static SPSC (Single-Producer Single-Consumer) channel.
2//!
3//! Const-generic ring buffer with atomic head/tail indices, split into
4//! `Sender`/`Receiver` halves. `send()`/`recv()` return real [`Future`]s
5//! usable with `.await` from `async fn` tasks — no heap allocation.
6//!
7//! ```ignore
8//! static CHAN: rivet::sync::Channel<u32, 8> = rivet::sync::Channel::new();
9//!
10//! #[rivet::task(priority = 1)]
11//! async fn producer() {
12//!     let (mut tx, _) = CHAN.split();
13//!     loop {
14//!         tx.send(42).await;
15//!     }
16//! }
17//!
18//! #[rivet::task(priority = 0)]
19//! async fn consumer() {
20//!     let (_, mut rx) = CHAN.split();
21//!     loop {
22//!         let val = rx.recv().await;
23//!     }
24//! }
25//! ```
26
27use core::cell::UnsafeCell;
28use core::future::Future;
29use core::mem::MaybeUninit;
30use core::pin::Pin;
31use core::task::{Context, Poll};
32
33use crate::waker;
34
35const NO_WAITER: u32 = 0xFFFF_FFFF;
36
37/// A lock-free SPSC ring buffer. Usable capacity is `N - 1`.
38pub struct Channel<T, const N: usize> {
39    buffer: UnsafeCell<[MaybeUninit<T>; N]>,
40    head: crate::sync::atomic::AtomicUsize,
41    tail: crate::sync::atomic::AtomicUsize,
42    /// One-shot split guard: `split()` succeeds exactly once (plan.md
43    /// [B8]) — the SPSC ownership invariant is enforced, not documented.
44    taken: crate::sync::atomic::AtomicBool,
45    /// Waiter blocked in `recv()` (encoded priority/index), woken by `send()`.
46    recv_waiter: crate::sync::atomic::AtomicU32,
47    /// Waiter blocked in `send()` (encoded priority/index), woken by `recv()`.
48    send_waiter: crate::sync::atomic::AtomicU32,
49}
50
51impl<T, const N: usize> Channel<T, N> {
52    /// Create a new empty channel.
53    #[cfg(not(loom))]
54    pub const fn new() -> Self {
55        Self {
56            buffer: UnsafeCell::new(unsafe {
57                // SAFETY: `MaybeUninit::uninit()` is valid to `assume_init`
58                // as a *value* of `MaybeUninit<T>` (not as a `T`); the
59                // buffer is only read through `assume_init_read` after a
60                // matching `write`, so no uninitialized `T` is ever
61                // observed.
62                MaybeUninit::uninit().assume_init()
63            }),
64            head: crate::sync::atomic::AtomicUsize::new(0),
65            tail: crate::sync::atomic::AtomicUsize::new(0),
66            taken: crate::sync::atomic::AtomicBool::new(false),
67            recv_waiter: crate::sync::atomic::AtomicU32::new(NO_WAITER),
68            send_waiter: crate::sync::atomic::AtomicU32::new(NO_WAITER),
69        }
70    }
71
72    /// Loom's atomics are not const-constructible; runtime constructor used
73    /// by the loom models.
74    #[cfg(loom)]
75    pub fn new() -> Self {
76        Self {
77            buffer: UnsafeCell::new(unsafe {
78                // SAFETY: see the non-loom `new` — the buffer is only read
79                // through `assume_init_read` after a matching `write`.
80                MaybeUninit::uninit().assume_init()
81            }),
82            head: crate::sync::atomic::AtomicUsize::new(0),
83            tail: crate::sync::atomic::AtomicUsize::new(0),
84            taken: crate::sync::atomic::AtomicBool::new(false),
85            recv_waiter: crate::sync::atomic::AtomicU32::new(NO_WAITER),
86            send_waiter: crate::sync::atomic::AtomicU32::new(NO_WAITER),
87        }
88    }
89
90    /// Split into sender and receiver halves — exactly once per channel
91    /// (plan.md [B8]). A second call returns `None`, enforcing the SPSC
92    /// ownership invariant at runtime instead of merely documenting it.
93    pub fn split(&'static self) -> Option<(Sender<'static, T, N>, Receiver<'static, T, N>)> {
94        if self.taken.swap(true, crate::sync::atomic::Ordering::AcqRel) {
95            return None;
96        }
97        Some((Sender { chan: self }, Receiver { chan: self }))
98    }
99}
100
101impl<T, const N: usize> Default for Channel<T, N> {
102    fn default() -> Self {
103        Self::new()
104    }
105}
106
107fn wake_waiter(slot: &crate::sync::atomic::AtomicU32) {
108    let w = slot.swap(NO_WAITER, crate::sync::atomic::Ordering::AcqRel);
109    if w != NO_WAITER {
110        waker::wake_task(crate::task::TaskId::from_u16(w as u16));
111    }
112}
113
114fn register_waiter(slot: &crate::sync::atomic::AtomicU32, id: crate::task::TaskId) {
115    slot.store(id.as_u16() as u32, crate::sync::atomic::Ordering::Release);
116}
117
118/// Sending half of an SPSC channel.
119pub struct Sender<'a, T, const N: usize> {
120    chan: &'a Channel<T, N>,
121}
122
123/// Receiving half of an SPSC channel.
124pub struct Receiver<'a, T, const N: usize> {
125    chan: &'a Channel<T, N>,
126}
127
128impl<'a, T, const N: usize> Sender<'a, T, N> {
129    /// Try to send without blocking. Returns `Ok(())` if sent, `Err(val)` if full.
130    pub fn try_send(&self, val: T) -> Result<(), T> {
131        let chan = self.chan;
132        let head = chan.head.load(crate::sync::atomic::Ordering::Acquire);
133        let tail = chan.tail.load(crate::sync::atomic::Ordering::Relaxed);
134        let next_tail = (tail + 1) % N;
135
136        if next_tail == head {
137            return Err(val);
138        }
139
140        // SAFETY: the slot at `tail` is free (the SPSC capacity check
141        // `next_tail != head` guarantees the receiver hasn't consumed past
142        // it), and only this sender writes; the value is published by the
143        // `tail.store(Release)` below.
144        unsafe {
145            (*chan.buffer.get())[tail].write(val);
146        }
147        chan.tail
148            .store(next_tail, crate::sync::atomic::Ordering::Release);
149        wake_waiter(&chan.recv_waiter);
150        Ok(())
151    }
152
153    /// Send a value, yielding the task if the channel is full.
154    ///
155    /// # Panics
156    /// Panics if polled outside of a task context while the channel is full.
157    pub fn send(&self, val: T) -> SendFut<'_, 'a, T, N> {
158        SendFut {
159            tx: self,
160            val: Some(val),
161            registered: false,
162        }
163    }
164}
165
166impl<'a, T, const N: usize> Receiver<'a, T, N> {
167    /// Try to receive without blocking.
168    pub fn try_recv(&self) -> Option<T> {
169        let chan = self.chan;
170        let head = chan.head.load(crate::sync::atomic::Ordering::Relaxed);
171        let tail = chan.tail.load(crate::sync::atomic::Ordering::Acquire);
172
173        if head == tail {
174            return None;
175        }
176
177        // SAFETY: the slot at `head` holds a value written by the sender
178        // and not yet consumed (SPSC: head < tail after the emptiness
179        // check); only this receiver reads, and `head.store(Release)`
180        // publishes the consumption.
181        let val = unsafe { (*chan.buffer.get())[head].assume_init_read() };
182        chan.head
183            .store((head + 1) % N, crate::sync::atomic::Ordering::Release);
184        wake_waiter(&chan.send_waiter);
185        Some(val)
186    }
187
188    /// Receive a value, yielding the task if the channel is empty.
189    ///
190    /// # Panics
191    /// Panics if polled outside of a task context while the channel is empty.
192    pub fn recv(&self) -> Recv<'_, 'a, T, N> {
193        Recv {
194            rx: self,
195            registered: false,
196        }
197    }
198}
199
200/// Future returned by [`Sender::send`].
201pub struct SendFut<'b, 'a, T, const N: usize> {
202    tx: &'b Sender<'a, T, N>,
203    val: Option<T>,
204    /// True while registered as a `send_waiter` (cleared on completion or
205    /// drop — plan.md §2.5: a cancelled send must not leave a stale waiter
206    /// that a later `recv` would spuriously wake).
207    registered: bool,
208}
209
210impl<'b, 'a, T, const N: usize> SendFut<'b, 'a, T, N> {
211    fn clear_registration(&self) {
212        if self.registered {
213            self.tx
214                .chan
215                .send_waiter
216                .store(NO_WAITER, crate::sync::atomic::Ordering::Release);
217        }
218    }
219}
220
221impl<'b, 'a, T, const N: usize> Drop for SendFut<'b, 'a, T, N> {
222    fn drop(&mut self) {
223        self.clear_registration();
224    }
225}
226
227impl<'b, 'a, T, const N: usize> Future for SendFut<'b, 'a, T, N> {
228    type Output = ();
229
230    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
231        // SAFETY: `SendFut` is not `Unpin`-sensitive — its fields (a
232        // `&Sender` and an `Option<T>`) are safe to move; the future is
233        // only ever polled through the pinned executor path, and the
234        // projection keeps the struct's own invariants intact.
235        let this = unsafe { self.get_unchecked_mut() };
236        let val = this
237            .val
238            .take()
239            .expect("Send future polled after completion");
240
241        match this.tx.try_send(val) {
242            Ok(()) => {
243                this.clear_registration();
244                Poll::Ready(())
245            }
246            Err(v) => {
247                let id = crate::executor::current_task()
248                    .expect("Sender::send().await polled outside of a task context");
249                register_waiter(&this.tx.chan.send_waiter, id);
250                this.registered = true;
251
252                // Re-check: recv() may have freed a slot between try_send and
253                // register_waiter above.
254                match this.tx.try_send(v) {
255                    Ok(()) => {
256                        this.clear_registration();
257                        Poll::Ready(())
258                    }
259                    Err(v) => {
260                        this.val = Some(v);
261                        Poll::Pending
262                    }
263                }
264            }
265        }
266    }
267}
268
269/// Future returned by [`Receiver::recv`].
270pub struct Recv<'b, 'a, T, const N: usize> {
271    rx: &'b Receiver<'a, T, N>,
272    /// Waiter registration to clear on drop (plan.md §2.5).
273    registered: bool,
274}
275
276impl<'b, 'a, T, const N: usize> Recv<'b, 'a, T, N> {
277    fn clear_registration(&self) {
278        if self.registered {
279            self.rx
280                .chan
281                .recv_waiter
282                .store(NO_WAITER, crate::sync::atomic::Ordering::Release);
283        }
284    }
285}
286
287impl<'b, 'a, T, const N: usize> Drop for Recv<'b, 'a, T, N> {
288    fn drop(&mut self) {
289        self.clear_registration();
290    }
291}
292
293impl<'b, 'a, T, const N: usize> Future for Recv<'b, 'a, T, N> {
294    type Output = T;
295
296    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<T> {
297        // SAFETY: `Recv` holds only a `&Receiver`, which is `Unpin`;
298        // moving it while pinned is sound.
299        let this = unsafe { self.get_unchecked_mut() };
300
301        if let Some(v) = this.rx.try_recv() {
302            this.clear_registration();
303            return Poll::Ready(v);
304        }
305
306        let id = crate::executor::current_task()
307            .expect("Receiver::recv().await polled outside of a task context");
308        register_waiter(&this.rx.chan.recv_waiter, id);
309        this.registered = true;
310
311        // Re-check: send() may have fired between try_recv and register_waiter.
312        if let Some(v) = this.rx.try_recv() {
313            this.clear_registration();
314            return Poll::Ready(v);
315        }
316
317        Poll::Pending
318    }
319}
320
321// Safety: Channel access is split between Sender (one owner) and Receiver
322// (one owner). Atomic head/tail/waiter fields give lock-free SPSC semantics.
323unsafe impl<T: Send, const N: usize> Sync for Channel<T, N> {}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    #[test]
330    fn send_recv_roundtrip() {
331        crate::kernel_test! {
332            static CHAN: Channel<u32, 8> = Channel::new();
333            let (tx, rx) = CHAN.split().expect("split once");
334
335            assert!(tx.try_send(42).is_ok());
336            assert_eq!(rx.try_recv(), Some(42));
337            assert_eq!(rx.try_recv(), None);
338        }
339    }
340
341    #[test]
342    fn full_channel_blocks_send() {
343        crate::kernel_test! {
344            static CHAN: Channel<u32, 3> = Channel::new(); // capacity = N-1 = 2
345            let (tx, rx) = CHAN.split().expect("split once");
346
347            assert!(tx.try_send(1).is_ok());
348            assert!(tx.try_send(2).is_ok());
349            assert_eq!(tx.try_send(3), Err(3));
350
351            assert_eq!(rx.try_recv(), Some(1));
352            assert!(tx.try_send(3).is_ok());
353            assert_eq!(rx.try_recv(), Some(2));
354            assert_eq!(rx.try_recv(), Some(3));
355        }
356    }
357
358    #[test]
359    fn empty_channel_try_recv_none() {
360        crate::kernel_test! {
361            static CHAN: Channel<u32, 4> = Channel::new();
362            let (_tx, rx) = CHAN.split().expect("split once");
363            assert_eq!(rx.try_recv(), None);
364        }
365    }
366
367    #[test]
368    fn recv_future_ready_when_data_present() {
369        crate::kernel_test! {
370            static CHAN: Channel<u32, 4> = Channel::new();
371            let (tx, rx) = CHAN.split().expect("split once");
372            tx.try_send(7).unwrap();
373
374            let waker = crate::waker::task_waker(crate::task::TaskId::new(0, 0));
375            let mut cx = Context::from_waker(&waker);
376            let mut fut = rx.recv();
377            // SAFETY: `fut` is a local `Recv` future; it is `Unpin`
378            // (holds only a `&mut Receiver`) and is never moved while
379            // pinned — sound for this single poll.
380            let pinned = unsafe { Pin::new_unchecked(&mut fut) };
381            assert_eq!(pinned.poll(&mut cx), Poll::Ready(7));
382        }
383    }
384
385    #[test]
386    #[should_panic(expected = "outside of a task context")]
387    fn recv_future_panics_without_task_context_when_empty() {
388        crate::kernel_test! {
389            static CHAN: Channel<u32, 4> = Channel::new();
390            let (_tx, rx) = CHAN.split().expect("split once");
391
392            let waker = crate::waker::task_waker(crate::task::TaskId::new(0, 0));
393            let mut cx = Context::from_waker(&waker);
394            let mut fut = rx.recv();
395            // SAFETY: `fut` is a local `Recv` future; `Unpin`, never
396            // moved while pinned — sound for this single poll.
397            let pinned = unsafe { Pin::new_unchecked(&mut fut) };
398            let _ = pinned.poll(&mut cx);
399        }
400    }
401}