subms_spsc_ring_buffer/lib.rs
1//! Wait-free SPSC ring buffer. Single producer, single consumer, bounded.
2//!
3//! Sized to a power of two; index modulo is a bitmask, not a `%`. Head and tail
4//! counters live on their own cache lines (128-byte padding) to keep the
5//! producer's writes from invalidating the consumer's read line. Each side
6//! caches the opposite index and only re-reads through the atomic when its own
7//! cache says full / empty.
8//!
9//! ```
10//! use subms_spsc_ring_buffer::SpscRingBuffer;
11//!
12//! let (mut tx, mut rx) = SpscRingBuffer::with_capacity(64);
13//! tx.try_push(42u32).unwrap();
14//! assert_eq!(rx.try_pop(), Some(42));
15//! assert_eq!(rx.try_pop(), None);
16//! ```
17//!
18//! Full writeup, design notes and measured benchmarks:
19//! <https://www.submillisecond.com/cookbook/recipes/subms-spsc-ring-buffer>
20
21use std::cell::UnsafeCell;
22use std::mem::MaybeUninit;
23use std::sync::Arc;
24use std::sync::atomic::{AtomicUsize, Ordering};
25
26/// 128 bytes is the right padding for Apple Silicon + recent x86 (some
27/// prefetchers operate on pairs of cache lines). Wastes ~96 bytes on 64-byte
28/// line machines; the cost is negligible against the false-sharing tax.
29#[repr(align(128))]
30pub(crate) struct Padded(pub(crate) AtomicUsize);
31
32pub(crate) struct Inner<T> {
33 /// Next slot the consumer will read. Written by the consumer only.
34 pub(crate) head: Padded,
35 /// Next slot the producer will write. Written by the producer only.
36 pub(crate) tail: Padded,
37 /// Power-of-two slot count.
38 pub(crate) capacity: usize,
39 /// `capacity - 1`; lets `idx & mask` replace `idx % capacity`.
40 pub(crate) mask: usize,
41 /// Slot storage. Each cell is touched by exactly one of producer/consumer
42 /// at any instant; `UnsafeCell` makes that explicit to the compiler.
43 pub(crate) buf: Box<[UnsafeCell<MaybeUninit<T>>]>,
44}
45
46// Producer/Consumer split is the type-level enforcement of SPSC. Inner itself
47// is `Sync` because the two sides touch disjoint state.
48unsafe impl<T: Send> Sync for Inner<T> {}
49unsafe impl<T: Send> Send for Inner<T> {}
50
51impl<T> Drop for Inner<T> {
52 fn drop(&mut self) {
53 // Drain remaining entries so their Drop impls fire.
54 let head = self.head.0.load(Ordering::Relaxed);
55 let tail = self.tail.0.load(Ordering::Relaxed);
56 for i in head..tail {
57 unsafe {
58 (*self.buf[i & self.mask].get()).assume_init_drop();
59 }
60 }
61 }
62}
63
64/// Producer handle. Move it to the producing thread; the type system enforces
65/// the SPSC invariant.
66pub struct Producer<T> {
67 pub(crate) inner: Arc<Inner<T>>,
68 /// Last-known consumer head; re-read on under-run only.
69 pub(crate) cached_head: usize,
70}
71
72unsafe impl<T: Send> Send for Producer<T> {}
73
74/// Consumer handle. Move it to the consuming thread.
75pub struct Consumer<T> {
76 pub(crate) inner: Arc<Inner<T>>,
77 /// Last-known producer tail; re-read on under-run only.
78 pub(crate) cached_tail: usize,
79}
80
81unsafe impl<T: Send> Send for Consumer<T> {}
82
83/// Constructor namespace. Use [`SpscRingBuffer::with_capacity`].
84pub struct SpscRingBuffer;
85
86impl SpscRingBuffer {
87 /// Build a (producer, consumer) pair backed by a buffer of at least
88 /// `requested_capacity` slots, rounded up to the next power of two.
89 /// A floor of 2 is enforced.
90 pub fn with_capacity<T>(requested_capacity: usize) -> (Producer<T>, Consumer<T>) {
91 let cap = requested_capacity.max(2).next_power_of_two();
92 let mut buf = Vec::with_capacity(cap);
93 for _ in 0..cap {
94 buf.push(UnsafeCell::new(MaybeUninit::<T>::uninit()));
95 }
96 let inner = Arc::new(Inner {
97 head: Padded(AtomicUsize::new(0)),
98 tail: Padded(AtomicUsize::new(0)),
99 capacity: cap,
100 mask: cap - 1,
101 buf: buf.into_boxed_slice(),
102 });
103 (
104 Producer {
105 inner: inner.clone(),
106 cached_head: 0,
107 },
108 Consumer {
109 inner,
110 cached_tail: 0,
111 },
112 )
113 }
114}
115
116impl<T> Producer<T> {
117 /// Push a value. Returns the input back as `Err(value)` if the buffer is
118 /// full. Wait-free: at most one atomic load + one atomic store.
119 pub fn try_push(&mut self, value: T) -> Result<(), T> {
120 let tail = self.inner.tail.0.load(Ordering::Relaxed);
121
122 // First check against the cached head - no atomic traffic if it's stale-but-not-full.
123 if tail.wrapping_sub(self.cached_head) == self.inner.capacity {
124 // Cache says full; re-read the real consumer head.
125 self.cached_head = self.inner.head.0.load(Ordering::Acquire);
126 if tail.wrapping_sub(self.cached_head) == self.inner.capacity {
127 return Err(value);
128 }
129 }
130
131 // Slot is ours; write the value, then publish the new tail.
132 unsafe {
133 (*self.inner.buf[tail & self.inner.mask].get()).write(value);
134 }
135 self.inner
136 .tail
137 .0
138 .store(tail.wrapping_add(1), Ordering::Release);
139 Ok(())
140 }
141
142 /// Total slot count (power of two; not the requested capacity).
143 pub fn capacity(&self) -> usize {
144 self.inner.capacity
145 }
146
147 /// Items currently buffered. A snapshot: the consumer runs concurrently, so
148 /// the true count can only be lower by the time the caller acts on it. Use
149 /// it for occupancy alarms and sizing, never to decide whether a push will
150 /// succeed - `try_push` already answers that without a race.
151 pub fn len(&self) -> usize {
152 occupancy(&self.inner)
153 }
154
155 /// True when no items are buffered. Snapshot semantics, as [`Producer::len`].
156 pub fn is_empty(&self) -> bool {
157 self.len() == 0
158 }
159
160 /// True when the ring holds `capacity` items. Snapshot semantics.
161 pub fn is_full(&self) -> bool {
162 self.len() == self.inner.capacity
163 }
164}
165
166/// Buffered count from the two published counters. Both loads are `Acquire`
167/// because either side may ask, so neither counter is reliably the caller's own.
168fn occupancy<T>(inner: &Inner<T>) -> usize {
169 let tail = inner.tail.0.load(Ordering::Acquire);
170 let head = inner.head.0.load(Ordering::Acquire);
171 tail.wrapping_sub(head)
172}
173
174impl<T> Consumer<T> {
175 /// Pop a value. Returns `None` if the buffer is empty. Wait-free.
176 pub fn try_pop(&mut self) -> Option<T> {
177 let head = self.inner.head.0.load(Ordering::Relaxed);
178
179 if head == self.cached_tail {
180 self.cached_tail = self.inner.tail.0.load(Ordering::Acquire);
181 if head == self.cached_tail {
182 return None;
183 }
184 }
185
186 let value = unsafe { (*self.inner.buf[head & self.inner.mask].get()).assume_init_read() };
187 self.inner
188 .head
189 .0
190 .store(head.wrapping_add(1), Ordering::Release);
191 Some(value)
192 }
193
194 /// Borrow the next value without consuming it. `None` when empty.
195 ///
196 /// Safe because only the consumer advances `head`, so the slot the returned
197 /// reference points at cannot be reclaimed while the borrow is live - the
198 /// `&mut self` receiver keeps `try_pop` out of reach for the same lifetime.
199 pub fn peek(&mut self) -> Option<&T> {
200 let head = self.inner.head.0.load(Ordering::Relaxed);
201 if head == self.cached_tail {
202 self.cached_tail = self.inner.tail.0.load(Ordering::Acquire);
203 if head == self.cached_tail {
204 return None;
205 }
206 }
207 unsafe { Some((*self.inner.buf[head & self.inner.mask].get()).assume_init_ref()) }
208 }
209
210 /// Drop every buffered item and return how many went. Consumer-side only:
211 /// the producer keeps publishing throughout, so anything it appends after
212 /// the counters were read stays in the ring.
213 pub fn clear(&mut self) -> usize {
214 let mut n = 0;
215 while self.try_pop().is_some() {
216 n += 1;
217 }
218 n
219 }
220
221 /// Total slot count (power of two).
222 pub fn capacity(&self) -> usize {
223 self.inner.capacity
224 }
225
226 /// Items currently buffered. Snapshot semantics, as [`Producer::len`] - the
227 /// producer runs concurrently, so the true count can only be higher.
228 pub fn len(&self) -> usize {
229 occupancy(&self.inner)
230 }
231
232 /// True when no items are buffered. Snapshot semantics.
233 pub fn is_empty(&self) -> bool {
234 self.len() == 0
235 }
236
237 /// True when the ring holds `capacity` items. Snapshot semantics.
238 pub fn is_full(&self) -> bool {
239 self.len() == self.inner.capacity
240 }
241}
242
243#[cfg(feature = "harness")]
244pub mod recipe;
245
246// Opt-in feature catalog. Each submodule is gated on its own Cargo
247// feature; the base ring stays wait-free SPSC + zero-dep std-only.
248#[cfg(any(
249 feature = "bulk",
250 feature = "wait-strategies",
251 feature = "mpsc-fan-in",
252 feature = "mpmc-disruptor",
253 feature = "metrics",
254))]
255pub mod features;
256
257#[cfg(feature = "metrics")]
258pub use features::metrics::{InstrumentedSpsc, RingMetrics, RingMetricsSnapshot};
259#[cfg(feature = "mpmc-disruptor")]
260pub use features::mpmc_disruptor::{DisruptorConsumer, DisruptorProducer, MpmcDisruptor};
261#[cfg(feature = "mpsc-fan-in")]
262pub use features::mpsc_fan_in::{MpscFanIn, MpscFanInConsumer, MpscFanInProducer};
263#[cfg(feature = "wait-strategies")]
264pub use features::wait_strategies::{
265 BlockingSpscConsumer, BlockingSpscProducer, BusySpin, ParkStrategy, WaitStrategy, YieldStrategy,
266};
267
268#[cfg(test)]
269#[path = "lib_tests.rs"]
270mod tests;
271
272#[cfg(test)]
273#[path = "sample_app_tests.rs"]
274mod sample_app_tests;