Skip to main content

spsc_ring/
lib.rs

1//! # spsc-ring
2//!
3//! Lock-free SPSC ring buffer.
4//!
5//! ## Design
6//!
7//! Sequence-number protocol: each slot carries a stamp that the producer writes
8//! *after* storing the value, and the consumer checks *before* reading.
9//! Acquire/Release ordering only (no `SeqCst`). Cache-line padding prevents
10//! false sharing between producer and consumer cursors.
11//!
12//! The SPSC contract is enforced at compile time: [`ring`] returns a
13//! `(Producer<T>, Consumer<T>)` pair. Each half is `Send` but not `Clone`.
14//!
15//! ## Example
16//!
17//! ```
18//! use spsc_ring::{ring, TryRecvError, TrySendError};
19//! use std::thread;
20//!
21//! let (tx, rx) = ring::<u64>(64).unwrap();
22//!
23//! thread::spawn(move || {
24//!     for i in 0..100u64 {
25//!         loop {
26//!             match tx.try_push(i) {
27//!                 Ok(()) => break,
28//!                 Err(TrySendError::Full(_)) => std::hint::spin_loop(),
29//!                 Err(TrySendError::Disconnected(_)) => return,
30//!             }
31//!         }
32//!     }
33//! });
34//!
35//! let mut received = Vec::new();
36//! while received.len() < 100 {
37//!     match rx.try_pop() {
38//!         Ok(v) => received.push(v),
39//!         Err(TryRecvError::Empty) => std::hint::spin_loop(),
40//!         Err(TryRecvError::Disconnected) => break,
41//!     }
42//! }
43//! assert_eq!(received, (0..100).collect::<Vec<_>>());
44//! ```
45
46#![deny(missing_docs)]
47#![allow(unsafe_code)]
48
49use std::cell::{Cell, UnsafeCell};
50use std::marker::PhantomData;
51use std::mem::MaybeUninit;
52use std::sync::Arc;
53use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
54
55const CACHE_LINE: usize = 64;
56
57#[repr(C)]
58struct PaddedAtomicUsize {
59    value: AtomicUsize,
60    _pad: [u8; CACHE_LINE - size_of::<AtomicUsize>()],
61}
62
63impl PaddedAtomicUsize {
64    const fn new(v: usize) -> Self {
65        Self {
66            value: AtomicUsize::new(v),
67            _pad: [0; CACHE_LINE - size_of::<AtomicUsize>()],
68        }
69    }
70}
71
72// 32-byte alignment: 2 slots per cache line. Halves false-sharing vs unpadded 16B slots
73// while keeping 1024-slot ring (32KB) within typical L1D cache.
74#[repr(align(32))]
75struct Slot<T> {
76    sequence: AtomicUsize,
77    value: UnsafeCell<MaybeUninit<T>>,
78}
79
80struct RingBuffer<T> {
81    slots: Box<[Slot<T>]>,
82    mask: usize,
83    closed: AtomicBool, // write-once at drop — grouped with write-once mask, away from hot head/tail lines
84    head: PaddedAtomicUsize,
85    tail: PaddedAtomicUsize,
86}
87
88// SAFETY: The SPSC contract is enforced by the type system — only one Producer
89// and one Consumer exist. The sequence-number protocol ensures no data race.
90unsafe impl<T: Send> Send for RingBuffer<T> {}
91unsafe impl<T: Send> Sync for RingBuffer<T> {}
92
93impl<T> Drop for RingBuffer<T> {
94    fn drop(&mut self) {
95        // Drain any items remaining in the buffer so their destructors run.
96        // head and tail are exclusively owned at this point (both Arc halves dropped).
97        let mut head = self.head.value.load(Ordering::Relaxed);
98        let tail = self.tail.value.load(Ordering::Relaxed);
99        while head != tail {
100            let slot = &self.slots[head & self.mask];
101            // SAFETY: head != tail means producer wrote this slot and consumer
102            // has not yet read it. No other thread is alive (both halves dropped).
103            unsafe { (*slot.value.get()).assume_init_drop() };
104            head = head.wrapping_add(1);
105        }
106    }
107}
108
109/// Strategy used by blocking [`Producer::push`] and [`Consumer::pop`] while waiting.
110#[derive(Debug, Clone, Copy)]
111pub enum WaitStrategy {
112    /// Spin with [`std::hint::spin_loop`]. Lowest latency, highest CPU burn.
113    SpinLoop,
114    /// Yield the thread with [`std::thread::yield_now`]. Balanced.
115    Yield,
116    /// Sleep for a fixed duration. Lowest CPU burn, highest latency.
117    Sleep(std::time::Duration),
118}
119
120impl WaitStrategy {
121    #[inline]
122    fn wait(&self) {
123        match self {
124            WaitStrategy::SpinLoop => std::hint::spin_loop(),
125            WaitStrategy::Yield => std::thread::yield_now(),
126            WaitStrategy::Sleep(d) => std::thread::sleep(*d),
127        }
128    }
129}
130
131/// Write half of the SPSC ring. Not `Clone` — only one producer exists.
132pub struct Producer<T> {
133    inner: Arc<RingBuffer<T>>,
134    _not_sync: PhantomData<Cell<()>>,
135}
136
137/// Read half of the SPSC ring. Not `Clone` — only one consumer exists.
138pub struct Consumer<T> {
139    inner: Arc<RingBuffer<T>>,
140    _not_sync: PhantomData<Cell<()>>,
141}
142
143/// Error returned by [`Consumer::pop`] when the producer has been dropped.
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub struct RecvError;
146
147impl std::fmt::Display for RecvError {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        write!(f, "producer disconnected")
150    }
151}
152
153impl std::error::Error for RecvError {}
154
155/// Error returned by [`Producer::push`] when the consumer has been dropped.
156#[derive(Debug, PartialEq, Eq)]
157pub struct SendError<T>(pub T);
158
159impl<T: std::fmt::Debug> std::fmt::Display for SendError<T> {
160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161        write!(f, "consumer disconnected; value: {:?}", self.0)
162    }
163}
164
165impl<T: std::fmt::Debug + 'static> std::error::Error for SendError<T> {}
166
167/// Error returned by [`Consumer::try_pop`].
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum TryRecvError {
170    /// Buffer is empty; try again later.
171    Empty,
172    /// Producer has been dropped; no more items will arrive.
173    Disconnected,
174}
175
176impl std::fmt::Display for TryRecvError {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        match self {
179            TryRecvError::Empty => write!(f, "buffer empty"),
180            TryRecvError::Disconnected => write!(f, "producer disconnected"),
181        }
182    }
183}
184
185impl std::error::Error for TryRecvError {}
186
187/// Error returned by [`Producer::try_push`].
188#[derive(Debug, PartialEq, Eq)]
189pub enum TrySendError<T> {
190    /// Buffer is full; value returned unchanged.
191    Full(T),
192    /// Consumer has been dropped; value returned unchanged.
193    Disconnected(T),
194}
195
196impl<T: std::fmt::Debug> std::fmt::Display for TrySendError<T> {
197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198        match self {
199            TrySendError::Full(v) => write!(f, "buffer full; value: {:?}", v),
200            TrySendError::Disconnected(v) => write!(f, "consumer disconnected; value: {:?}", v),
201        }
202    }
203}
204
205impl<T: std::fmt::Debug + 'static> std::error::Error for TrySendError<T> {}
206
207/// Error returned by [`ring`] when capacity is zero or not a power of two.
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209pub struct InvalidCapacity(pub usize);
210
211impl std::fmt::Display for InvalidCapacity {
212    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213        write!(f, "capacity {} is not a non-zero power of two", self.0)
214    }
215}
216
217impl std::error::Error for InvalidCapacity {}
218
219impl<T> Producer<T> {
220    /// Returns `true` if the consumer has been dropped.
221    #[must_use]
222    pub fn is_disconnected(&self) -> bool {
223        self.inner.closed.load(Ordering::Acquire)
224    }
225
226    /// Push a value. Returns `Err` if the buffer is full or the consumer has been dropped.
227    ///
228    /// # Errors
229    ///
230    /// - [`TrySendError::Full`] — buffer is full; value is returned unchanged.
231    /// - [`TrySendError::Disconnected`] — consumer has been dropped; value is returned unchanged.
232    #[inline]
233    pub fn try_push(&self, value: T) -> Result<(), TrySendError<T>> {
234        if self.inner.closed.load(Ordering::Acquire) {
235            return Err(TrySendError::Disconnected(value));
236        }
237        let rb = &*self.inner;
238        let tail = rb.tail.value.load(Ordering::Relaxed);
239        let slot = &rb.slots[tail & rb.mask];
240        let seq = slot.sequence.load(Ordering::Acquire);
241
242        if seq != tail {
243            return Err(TrySendError::Full(value));
244        }
245
246        // SAFETY: Sole producer. Sequence check guarantees consumer finished reading this slot.
247        unsafe { (*slot.value.get()).write(value) };
248        slot.sequence.store(tail + 1, Ordering::Release);
249        rb.tail.value.store(tail + 1, Ordering::Relaxed);
250        Ok(())
251    }
252
253    /// Approximate number of items currently in the buffer.
254    ///
255    /// # Why approximate
256    ///
257    /// Reads both `tail` (owned by the producer) and `head` (owned by the
258    /// consumer) with [`Ordering::Relaxed`]. The returned value can differ
259    /// from the true count in either direction: a concurrent pop can lower it,
260    /// and a stale Relaxed read of `head` can raise it. The result is a
261    /// best-effort snapshot, not a linearizable read.
262    ///
263    /// # Safe uses
264    ///
265    /// - Capacity planning and monitoring dashboards.
266    /// - Backpressure hints (e.g., slow down if `len() > threshold`).
267    ///
268    /// # Must NOT be used for
269    ///
270    /// - Deciding whether `try_push` will succeed — use the `Err` return value
271    ///   of `try_push` instead.
272    /// - Any correctness decision that requires an exact count.
273    ///
274    /// # Example
275    ///
276    /// ```
277    /// use spsc_ring::ring;
278    /// let (tx, _rx) = ring::<u32>(16).unwrap();
279    /// tx.try_push(1).unwrap();
280    /// tx.try_push(2).unwrap();
281    /// // len() is a hint — do not assert == 2 across threads.
282    /// let _ = tx.len(); // safe: backpressure hint only
283    /// ```
284    #[must_use]
285    pub fn len(&self) -> usize {
286        let rb = &*self.inner;
287        let tail = rb.tail.value.load(Ordering::Relaxed);
288        let head = rb.head.value.load(Ordering::Relaxed);
289        tail.wrapping_sub(head)
290    }
291
292    /// Returns `true` if the buffer appears empty.
293    #[must_use]
294    pub fn is_empty(&self) -> bool {
295        self.len() == 0
296    }
297
298    /// Returns `true` if the buffer appears full.
299    #[must_use]
300    pub fn is_full(&self) -> bool {
301        self.len() == self.capacity()
302    }
303
304    /// Buffer capacity.
305    #[must_use]
306    pub fn capacity(&self) -> usize {
307        self.inner.mask + 1
308    }
309
310    /// Push a value, blocking with `strategy` until a slot is free.
311    ///
312    /// # Errors
313    ///
314    /// Returns [`SendError`] containing the value if the consumer has been dropped.
315    pub fn push(&self, value: T, strategy: &WaitStrategy) -> Result<(), SendError<T>> {
316        let mut v = value;
317        loop {
318            match self.try_push(v) {
319                Ok(()) => return Ok(()),
320                Err(TrySendError::Disconnected(returned)) => return Err(SendError(returned)),
321                Err(TrySendError::Full(returned)) => {
322                    strategy.wait();
323                    v = returned;
324                }
325            }
326        }
327    }
328}
329
330impl<T> Drop for Producer<T> {
331    fn drop(&mut self) {
332        self.inner.closed.store(true, Ordering::Release);
333    }
334}
335
336impl<T: Copy> Producer<T> {
337    /// Push as many items from `src` as fit. Returns count pushed.
338    ///
339    /// Stops early if the buffer is full or the consumer has been dropped.
340    /// If `count < src.len()`, call [`Producer::is_disconnected`] to distinguish
341    /// the two cases — a full buffer is retriable, a disconnect is permanent.
342    #[inline]
343    pub fn push_slice(&self, src: &[T]) -> usize {
344        let mut count = 0;
345        for &item in src {
346            match self.try_push(item) {
347                Ok(()) => count += 1,
348                Err(_) => break,
349            }
350        }
351        count
352    }
353}
354
355impl<T> Drop for Consumer<T> {
356    fn drop(&mut self) {
357        self.inner.closed.store(true, Ordering::Release);
358    }
359}
360
361impl<T> Consumer<T> {
362    /// Returns `true` if the producer has been dropped.
363    #[must_use]
364    pub fn is_disconnected(&self) -> bool {
365        self.inner.closed.load(Ordering::Acquire)
366    }
367
368    /// Pop a value.
369    ///
370    /// # Errors
371    ///
372    /// - [`TryRecvError::Empty`] — buffer is empty; try again later.
373    /// - [`TryRecvError::Disconnected`] — producer has been dropped and buffer is empty.
374    #[inline]
375    pub fn try_pop(&self) -> Result<T, TryRecvError> {
376        let rb = &*self.inner;
377        let head = rb.head.value.load(Ordering::Relaxed);
378        let slot = &rb.slots[head & rb.mask];
379        let seq = slot.sequence.load(Ordering::Acquire);
380
381        if seq != head + 1 {
382            if rb.closed.load(Ordering::Acquire) {
383                // Re-check seq: the Acquire on closed synchronizes with the producer's
384                // Release writes, making any last push visible. Without this re-check,
385                // items written just before the producer dropped could be silently lost.
386                if slot.sequence.load(Ordering::Acquire) == head + 1 {
387                    let value = unsafe { (*slot.value.get()).assume_init_read() };
388                    slot.sequence.store(head + rb.mask + 1, Ordering::Release);
389                    rb.head.value.store(head + 1, Ordering::Relaxed);
390                    return Ok(value);
391                }
392                return Err(TryRecvError::Disconnected);
393            }
394            return Err(TryRecvError::Empty);
395        }
396
397        // SAFETY: Sole consumer. Sequence check guarantees producer finished writing.
398        // assume_init_read performs a bitwise copy — safe because the producer wrote
399        // a valid T and we release the slot immediately after, ensuring no double-read.
400        let value = unsafe { (*slot.value.get()).assume_init_read() };
401        slot.sequence.store(head + rb.mask + 1, Ordering::Release);
402        rb.head.value.store(head + 1, Ordering::Relaxed);
403        Ok(value)
404    }
405
406    /// Approximate number of items currently in the buffer.
407    ///
408    /// # Why approximate
409    ///
410    /// Reads both `tail` (owned by the producer) and `head` (owned by the
411    /// consumer) with [`Ordering::Relaxed`]. The producer may have advanced
412    /// `tail` between the two loads, so the returned value can be *lower* than
413    /// the true count. The result is a best-effort snapshot, not a
414    /// linearizable read.
415    ///
416    /// # Safe uses
417    ///
418    /// - Capacity planning and monitoring dashboards.
419    /// - Backpressure hints (e.g., slow down if `len() < threshold` before
420    ///   sleeping).
421    ///
422    /// # Must NOT be used for
423    ///
424    /// - Deciding whether `try_pop` will return `Ok` — use the `Err` return
425    ///   value of `try_pop` instead.
426    /// - Any correctness decision that requires an exact count.
427    ///
428    /// # Example
429    ///
430    /// ```
431    /// use spsc_ring::ring;
432    /// let (tx, rx) = ring::<u32>(16).unwrap();
433    /// tx.try_push(1).unwrap();
434    /// tx.try_push(2).unwrap();
435    /// // len() is a hint — do not assert == 2 across threads.
436    /// let _ = rx.len(); // safe: backpressure hint only
437    /// ```
438    #[must_use]
439    pub fn len(&self) -> usize {
440        let rb = &*self.inner;
441        let tail = rb.tail.value.load(Ordering::Relaxed);
442        let head = rb.head.value.load(Ordering::Relaxed);
443        tail.wrapping_sub(head)
444    }
445
446    /// Returns `true` if the buffer appears empty.
447    #[must_use]
448    pub fn is_empty(&self) -> bool {
449        self.len() == 0
450    }
451
452    /// Buffer capacity.
453    #[must_use]
454    pub fn capacity(&self) -> usize {
455        self.inner.mask + 1
456    }
457
458    /// Pop a value, blocking with `strategy` until one is available.
459    ///
460    /// # Errors
461    ///
462    /// Returns [`RecvError`] if the producer has been dropped and the buffer is empty.
463    pub fn pop(&self, strategy: &WaitStrategy) -> Result<T, RecvError> {
464        loop {
465            match self.try_pop() {
466                Ok(v) => return Ok(v),
467                Err(TryRecvError::Disconnected) => return Err(RecvError),
468                Err(TryRecvError::Empty) => strategy.wait(),
469            }
470        }
471    }
472}
473
474impl<T: Copy> Consumer<T> {
475    /// Pop as many items into `dst` as are available. Returns count popped.
476    ///
477    /// Stops early if the buffer is empty or the producer has been dropped.
478    /// If `count < dst.len()`, call [`Consumer::is_disconnected`] to distinguish
479    /// the two cases — an empty buffer may refill, a disconnect will not.
480    #[inline]
481    pub fn pop_into_slice(&self, dst: &mut [T]) -> usize {
482        let mut count = 0;
483        for slot in dst.iter_mut() {
484            match self.try_pop() {
485                Ok(v) => {
486                    *slot = v;
487                    count += 1;
488                }
489                Err(_) => break,
490            }
491        }
492        count
493    }
494}
495
496/// Create an SPSC ring buffer with the given capacity (must be a power of 2).
497///
498/// Returns `(Producer, Consumer)` — send each half to its own thread.
499///
500/// # Errors
501///
502/// Returns [`InvalidCapacity`] if `capacity` is zero or not a power of two.
503pub fn ring<T: Send>(capacity: usize) -> Result<(Producer<T>, Consumer<T>), InvalidCapacity> {
504    if capacity == 0 || !capacity.is_power_of_two() {
505        return Err(InvalidCapacity(capacity));
506    }
507
508    let slots: Vec<Slot<T>> = (0..capacity)
509        .map(|i| Slot {
510            sequence: AtomicUsize::new(i),
511            value: UnsafeCell::new(MaybeUninit::uninit()),
512        })
513        .collect();
514
515    let inner = Arc::new(RingBuffer {
516        slots: slots.into_boxed_slice(),
517        mask: capacity - 1,
518        head: PaddedAtomicUsize::new(0),
519        tail: PaddedAtomicUsize::new(0),
520        closed: AtomicBool::new(false),
521    });
522
523    Ok((
524        Producer {
525            inner: Arc::clone(&inner),
526            _not_sync: PhantomData,
527        },
528        Consumer {
529            inner,
530            _not_sync: PhantomData,
531        },
532    ))
533}
534
535#[cfg(test)]
536mod tests {
537    use super::*;
538    use std::thread;
539
540    #[test]
541    fn push_pop_single() {
542        let (tx, rx) = ring(4).unwrap();
543        assert!(tx.try_push(42).is_ok());
544        assert_eq!(rx.try_pop(), Ok(42));
545    }
546
547    #[test]
548    fn full_returns_err() {
549        let (tx, _rx) = ring(2).unwrap();
550        assert!(tx.try_push(1).is_ok());
551        assert!(tx.try_push(2).is_ok());
552        assert_eq!(tx.try_push(3), Err(TrySendError::Full(3)));
553    }
554
555    #[test]
556    fn empty_returns_none() {
557        let (_tx, rx) = ring::<i32>(4).unwrap();
558        assert_eq!(rx.try_pop(), Err(TryRecvError::Empty));
559    }
560
561    #[test]
562    fn fifo_order() {
563        let (tx, rx) = ring(4).unwrap();
564        for i in 0..4 {
565            tx.try_push(i).unwrap();
566        }
567        for i in 0..4 {
568            assert_eq!(rx.try_pop(), Ok(i));
569        }
570    }
571
572    #[test]
573    fn wrap_around() {
574        let (tx, rx) = ring(4).unwrap();
575        for round in 0..3 {
576            for i in 0..4 {
577                tx.try_push(round * 4 + i).unwrap();
578            }
579            for i in 0..4 {
580                assert_eq!(rx.try_pop(), Ok(round * 4 + i));
581            }
582        }
583    }
584
585    #[test]
586    fn concurrent_spsc() {
587        let (tx, rx) = ring(64).unwrap();
588        let count = 100_000;
589
590        let producer = thread::spawn(move || {
591            for i in 0..count {
592                loop {
593                    match tx.try_push(i) {
594                        Ok(()) => break,
595                        Err(TrySendError::Full(_)) => std::hint::spin_loop(),
596                        Err(TrySendError::Disconnected(_)) => return,
597                    }
598                }
599            }
600        });
601
602        let consumer = thread::spawn(move || {
603            let mut received = Vec::with_capacity(count);
604            while received.len() < count {
605                match rx.try_pop() {
606                    Ok(v) => received.push(v),
607                    Err(TryRecvError::Empty) => std::hint::spin_loop(),
608                    Err(TryRecvError::Disconnected) => break,
609                }
610            }
611            received
612        });
613
614        producer.join().unwrap();
615        let received = consumer.join().unwrap();
616        let expected: Vec<usize> = (0..count).collect();
617        assert_eq!(received, expected);
618    }
619
620    #[test]
621    fn len_and_capacity() {
622        let (tx, rx) = ring(4).unwrap();
623        assert_eq!(tx.capacity(), 4);
624        assert!(rx.is_empty());
625        tx.try_push(1).unwrap();
626        assert_eq!(rx.len(), 1);
627        tx.try_push(2).unwrap();
628        tx.try_push(3).unwrap();
629        tx.try_push(4).unwrap();
630        assert!(tx.is_full());
631    }
632
633    #[test]
634    fn push_slice_all_fit() {
635        let (tx, rx) = ring(8).unwrap();
636        let data = [1u32, 2, 3, 4];
637        let pushed = tx.push_slice(&data);
638        assert_eq!(pushed, 4);
639        for &expected in &data {
640            assert_eq!(rx.try_pop(), Ok(expected));
641        }
642    }
643
644    #[test]
645    fn push_slice_partial_when_full() {
646        let (tx, _rx) = ring(2).unwrap();
647        let _ = tx.push_slice(&[10u32, 20]);
648        let pushed = tx.push_slice(&[30u32, 40]);
649        assert_eq!(pushed, 0);
650    }
651
652    #[test]
653    fn pop_into_slice_all_available() {
654        let (tx, rx) = ring(8).unwrap();
655        for i in 0..4u32 {
656            tx.try_push(i).unwrap();
657        }
658        let mut dst = [0u32; 4];
659        let popped = rx.pop_into_slice(&mut dst);
660        assert_eq!(popped, 4);
661        assert_eq!(dst, [0, 1, 2, 3]);
662    }
663
664    #[test]
665    fn pop_into_slice_empty_buffer() {
666        let (_tx, rx) = ring::<u32>(4).unwrap();
667        let mut dst = [0u32; 4];
668        let popped = rx.pop_into_slice(&mut dst);
669        assert_eq!(popped, 0);
670        assert_eq!(dst, [0u32; 4]);
671    }
672
673    #[test]
674    fn pop_into_slice_partial_dst() {
675        let (tx, rx) = ring(8).unwrap();
676        for i in 0..6u32 {
677            tx.try_push(i).unwrap();
678        }
679        let mut dst = [0u32; 3];
680        let popped = rx.pop_into_slice(&mut dst);
681        assert_eq!(popped, 3);
682        assert_eq!(dst, [0, 1, 2]);
683        assert_eq!(rx.try_pop(), Ok(3));
684    }
685
686    #[test]
687    fn push_pop_slice_roundtrip_concurrent() {
688        let (tx, rx) = ring(256).unwrap();
689        let data: Vec<u32> = (0..1024).collect();
690        let data_clone = data.clone();
691
692        let producer = thread::spawn(move || {
693            let mut sent = 0;
694            while sent < data_clone.len() {
695                sent += tx.push_slice(&data_clone[sent..]);
696                std::hint::spin_loop();
697            }
698        });
699
700        let consumer = thread::spawn(move || {
701            let mut received = Vec::with_capacity(1024);
702            let mut buf = [0u32; 32];
703            while received.len() < 1024 {
704                let n = rx.pop_into_slice(&mut buf);
705                received.extend_from_slice(&buf[..n]);
706                std::hint::spin_loop();
707            }
708            received
709        });
710
711        producer.join().unwrap();
712        let received = consumer.join().unwrap();
713        let expected: Vec<u32> = (0..1024).collect();
714        assert_eq!(received, expected);
715    }
716
717    #[test]
718    fn ring_returns_err_on_non_power_of_two() {
719        assert!(ring::<u32>(3).is_err());
720        assert!(ring::<u32>(0).is_err());
721    }
722
723    #[test]
724    fn ring_returns_ok_on_valid_capacity() {
725        assert!(ring::<u32>(4).is_ok());
726        assert!(ring::<u32>(1).is_ok());
727    }
728
729    #[test]
730    fn is_disconnected_false_while_both_live() {
731        let (tx, rx) = ring::<u32>(4).unwrap();
732        assert!(!tx.is_disconnected());
733        assert!(!rx.is_disconnected());
734    }
735
736    #[test]
737    fn is_disconnected_true_after_drop() {
738        let (tx, rx) = ring::<u32>(4).unwrap();
739        drop(rx);
740        assert!(tx.is_disconnected());
741    }
742
743    #[test]
744    fn producer_drop_signals_disconnected() {
745        let (tx, rx) = ring::<u32>(4).unwrap();
746        drop(tx);
747        assert_eq!(rx.try_pop(), Err(TryRecvError::Disconnected));
748    }
749
750    #[test]
751    fn consumer_drop_signals_disconnected() {
752        let (tx, rx) = ring::<u32>(4).unwrap();
753        drop(rx);
754        assert_eq!(tx.try_push(1), Err(TrySendError::Disconnected(1)));
755    }
756
757    #[test]
758    fn try_pop_returns_empty_when_buffer_empty() {
759        let (_tx, rx) = ring::<u32>(4).unwrap();
760        assert_eq!(rx.try_pop(), Err(TryRecvError::Empty));
761    }
762
763    #[test]
764    fn try_push_returns_full_when_buffer_full() {
765        let (tx, _rx) = ring::<u32>(2).unwrap();
766        tx.try_push(1).unwrap();
767        tx.try_push(2).unwrap();
768        assert_eq!(tx.try_push(3), Err(TrySendError::Full(3)));
769    }
770
771    #[test]
772    fn pop_returns_recv_error_on_disconnect() {
773        let (tx, rx) = ring::<u32>(4).unwrap();
774        drop(tx);
775        assert_eq!(rx.pop(&WaitStrategy::SpinLoop), Err(RecvError));
776    }
777
778    #[test]
779    fn push_returns_send_error_on_disconnect() {
780        let (tx, rx) = ring::<u32>(4).unwrap();
781        drop(rx);
782        assert_eq!(tx.push(42, &WaitStrategy::SpinLoop), Err(SendError(42)));
783    }
784
785    #[test]
786    fn pop_returns_value_before_checking_disconnect() {
787        let (tx, rx) = ring::<u32>(4).unwrap();
788        tx.try_push(99).unwrap();
789        drop(tx);
790        assert_eq!(rx.pop(&WaitStrategy::SpinLoop), Ok(99));
791        assert_eq!(rx.pop(&WaitStrategy::SpinLoop), Err(RecvError));
792    }
793
794    #[test]
795    fn try_pop_drains_buffered_items_after_producer_drop() {
796        let (tx, rx) = ring::<u32>(4).unwrap();
797        tx.try_push(1).unwrap();
798        tx.try_push(2).unwrap();
799        tx.try_push(3).unwrap();
800        drop(tx);
801        assert_eq!(rx.try_pop(), Ok(1));
802        assert_eq!(rx.try_pop(), Ok(2));
803        assert_eq!(rx.try_pop(), Ok(3));
804        assert_eq!(rx.try_pop(), Err(TryRecvError::Disconnected));
805    }
806
807    #[test]
808    fn error_types_exist() {
809        let _: RecvError = RecvError;
810        let _: SendError<u32> = SendError(42);
811        let _: TryRecvError = TryRecvError::Empty;
812        let _: TryRecvError = TryRecvError::Disconnected;
813        let _: TrySendError<u32> = TrySendError::Full(1);
814        let _: TrySendError<u32> = TrySendError::Disconnected(2);
815    }
816
817    #[test]
818    fn wait_strategy_is_copy() {
819        let s = WaitStrategy::Sleep(std::time::Duration::from_millis(1));
820        let _a = s;
821        let _b = s; // would fail to compile if not Copy
822    }
823
824    #[test]
825    fn push_slice_partial_fit() {
826        let (tx, rx) = ring(4).unwrap();
827        tx.push_slice(&[1u32, 2]);
828        let pushed = tx.push_slice(&[3u32, 4, 5, 6]);
829        assert_eq!(pushed, 2);
830        assert_eq!(rx.try_pop(), Ok(1));
831        assert_eq!(rx.try_pop(), Ok(2));
832        assert_eq!(rx.try_pop(), Ok(3));
833        assert_eq!(rx.try_pop(), Ok(4));
834        assert_eq!(rx.try_pop(), Err(TryRecvError::Empty));
835    }
836
837    #[test]
838    #[cfg_attr(miri, ignore)] // sleep-based timing; Miri doesn't simulate real time
839    fn wait_strategy_spin_loop_waits_until_slot_free() {
840        let (tx, rx) = ring(2).unwrap();
841        tx.try_push(1).unwrap();
842        tx.try_push(2).unwrap();
843
844        let consumer = std::thread::spawn(move || {
845            std::thread::sleep(std::time::Duration::from_millis(5));
846            rx.try_pop().unwrap();
847            rx
848        });
849
850        tx.push(3, &WaitStrategy::SpinLoop).unwrap();
851        let rx = consumer.join().unwrap();
852        assert_eq!(rx.try_pop(), Ok(2));
853        assert_eq!(rx.try_pop(), Ok(3));
854    }
855
856    #[test]
857    #[cfg_attr(miri, ignore)] // sleep-based timing; Miri doesn't simulate real time
858    fn wait_strategy_yield_waits_until_value_available() {
859        let (tx, rx) = ring(4).unwrap();
860
861        let producer = std::thread::spawn(move || {
862            std::thread::sleep(std::time::Duration::from_millis(5));
863            tx.try_push(42).unwrap();
864        });
865
866        let value = rx.pop(&WaitStrategy::Yield).unwrap();
867        assert_eq!(value, 42u32);
868        producer.join().unwrap();
869    }
870
871    #[test]
872    fn wait_strategy_sleep_push_pop() {
873        use std::time::Duration;
874        let (tx, rx) = ring(4).unwrap();
875        tx.push(99u64, &WaitStrategy::Sleep(Duration::from_millis(1)))
876            .unwrap();
877        assert_eq!(
878            rx.pop(&WaitStrategy::Sleep(Duration::from_millis(1)))
879                .unwrap(),
880            99u64
881        );
882    }
883
884    #[test]
885    #[cfg_attr(miri, ignore)] // sleep-based timing; Miri doesn't simulate real time
886    fn wait_strategy_sleep_exercises_spin_on_full_buffer() {
887        use std::time::Duration;
888        let (tx, rx) = ring(2).unwrap();
889        tx.try_push(1u32).unwrap();
890        tx.try_push(2u32).unwrap();
891
892        // Consumer drains after a delay — forces push to spin-sleep before slot is free.
893        let consumer = std::thread::spawn(move || {
894            std::thread::sleep(Duration::from_millis(10));
895            rx.try_pop().unwrap();
896            rx
897        });
898
899        tx.push(3u32, &WaitStrategy::Sleep(Duration::from_millis(1)))
900            .unwrap();
901        let rx = consumer.join().unwrap();
902        assert_eq!(rx.try_pop(), Ok(2));
903        assert_eq!(rx.try_pop(), Ok(3));
904    }
905
906    #[test]
907    fn push_slice_stops_on_disconnect() {
908        let (tx, rx) = ring::<u32>(8).unwrap();
909        drop(rx);
910        // Buffer is empty and consumer dropped — first try_push returns Disconnected.
911        let pushed = tx.push_slice(&[1, 2, 3, 4]);
912        assert_eq!(pushed, 0);
913        assert!(tx.is_disconnected());
914    }
915
916    #[test]
917    fn pop_into_slice_stops_on_disconnect() {
918        let (tx, rx) = ring::<u32>(8).unwrap();
919        tx.try_push(10).unwrap();
920        tx.try_push(20).unwrap();
921        drop(tx);
922        let mut dst = [0u32; 4];
923        // Drains buffered items, then stops at Disconnected.
924        let popped = rx.pop_into_slice(&mut dst);
925        assert_eq!(popped, 2);
926        assert_eq!(dst[0], 10);
927        assert_eq!(dst[1], 20);
928    }
929
930    #[test]
931    fn both_halves_drop_simultaneously() {
932        // Dropping both ends from separate threads must not double-free or panic.
933        let (tx, rx) = ring::<u32>(4).unwrap();
934        let t1 = std::thread::spawn(move || drop(tx));
935        let t2 = std::thread::spawn(move || drop(rx));
936        t1.join().unwrap();
937        t2.join().unwrap();
938    }
939
940    #[test]
941    fn ring_capacity_one_wraps_correctly() {
942        let (tx, rx) = ring::<u32>(1).unwrap();
943        for i in 0..4u32 {
944            tx.try_push(i).unwrap();
945            assert_eq!(rx.try_pop(), Ok(i));
946        }
947        assert_eq!(rx.try_pop(), Err(TryRecvError::Empty));
948    }
949
950    #[test]
951    fn push_slice_empty_is_noop() {
952        let (tx, rx) = ring::<u32>(4).unwrap();
953        assert_eq!(tx.push_slice(&[]), 0);
954        assert_eq!(rx.try_pop(), Err(TryRecvError::Empty));
955    }
956
957    #[test]
958    fn pop_into_slice_empty_is_noop() {
959        let (tx, rx) = ring::<u32>(4).unwrap();
960        tx.try_push(1).unwrap();
961        assert_eq!(rx.pop_into_slice(&mut []), 0);
962        assert_eq!(rx.try_pop(), Ok(1));
963    }
964}
965
966#[cfg(loom)]
967mod loom_tests {
968    use super::ring;
969    use loom::thread;
970
971    /// Loom explores all thread interleavings of a single push followed by a
972    /// single pop on a 2-slot buffer.  A 2-slot buffer is the smallest
973    /// power-of-two that lets both slots be exercised.  Keep the iteration
974    /// count tiny — loom's state space is exponential in the number of
975    /// synchronisation operations.
976    #[test]
977    fn push_then_pop_all_interleavings() {
978        loom::model(|| {
979            let (tx, rx) = ring(2).expect("valid capacity");
980
981            let producer = thread::spawn(move || {
982                tx.try_push(42usize).ok();
983            });
984
985            let consumer = thread::spawn(move || rx.try_pop());
986
987            producer.join().unwrap();
988            let _result = consumer.join().unwrap();
989        });
990    }
991
992    /// Producer pushes two items; consumer pops both.  Verifies wrap-around
993    /// under loom's scheduler.
994    #[test]
995    fn push_pop_two_items() {
996        loom::model(|| {
997            let (tx, rx) = ring(2).expect("valid capacity");
998
999            let producer = thread::spawn(move || {
1000                loop {
1001                    match tx.try_push(1usize) {
1002                        Ok(()) => break,
1003                        Err(_) => loom::hint::spin_loop(),
1004                    }
1005                }
1006                loop {
1007                    match tx.try_push(2usize) {
1008                        Ok(()) => break,
1009                        Err(_) => loom::hint::spin_loop(),
1010                    }
1011                }
1012            });
1013
1014            let consumer = thread::spawn(move || {
1015                let mut got = Vec::new();
1016                while got.len() < 2 {
1017                    match rx.try_pop() {
1018                        Ok(v) => got.push(v),
1019                        Err(_) => loom::hint::spin_loop(),
1020                    }
1021                }
1022                got
1023            });
1024
1025            producer.join().unwrap();
1026            let got = consumer.join().unwrap();
1027            assert_eq!(got, vec![1, 2]);
1028        });
1029    }
1030}