low_latency_data_structures/spmc/consumer.rs
1use std::marker::PhantomData;
2use std::sync::Arc;
3use std::sync::atomic::{Ordering, fence};
4
5use crossbeam_utils::CachePadded;
6
7use crate::mem::{Allocation, Allocator};
8use crate::spmc::queue::{Queue, Slot};
9
10pub(super) type ConsumerState = CachePadded<ConsumerStateInner>;
11
12#[derive(Clone)]
13pub(super) struct ConsumerStateInner {
14 read_cursor: usize,
15 cached_write_cursor: usize,
16}
17
18/// Outcome of a [`Consumer::try_read`] call.
19///
20/// `try_read` is non-blocking and never allocates, so the return value
21/// distinguishes three cases the caller must handle: a successful read, an
22/// empty queue, or a detected lap. The enum is `#[must_use]` because
23/// silently dropping `Value` would lose the only copy of the data, and
24/// silently dropping `Lapped` would hide message loss.
25#[must_use = "the returned ReadResult indicates whether a value was read, the queue was empty, or the consumer was lapped"]
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ReadResult<T> {
28 /// A value was successfully read from the next slot.
29 Value(T),
30 /// The queue is currently empty.
31 Empty,
32 /// The producer lapped this consumer at least once. The read cursor has
33 /// been advanced to the most recently published slot; `skipped` is the
34 /// number of values that were never observed.
35 Lapped {
36 /// Number of values skipped over by the lap-recovery jump.
37 skipped: usize,
38 },
39}
40
41/// A reading handle of an SPMC broadcast queue.
42///
43/// Each consumer has its own private read cursor: every consumer observes
44/// every value the producer publishes, independently, unless it falls behind
45/// by more than `CAPACITY` slots.
46///
47/// `Consumer` is [`Send`] but not [`Sync`]: at most one thread may call
48/// [`try_read`](Self::try_read) on a given consumer at a time. To get more
49/// consumers, clone this one. Each clone inherits the parent's current read
50/// cursor at the time of the clone, so a clone made after the parent has
51/// read `N` values will not re-observe those `N` values.
52pub struct Consumer<T, const CAPACITY: usize, A>
53where
54 T: bytemuck::AnyBitPattern,
55 A: Allocator,
56{
57 state: ConsumerState,
58 inner: Arc<Queue<T, CAPACITY, A>>,
59 _not_sync: PhantomData<*const ()>,
60}
61
62impl<T, const CAPACITY: usize, A> std::fmt::Debug for Consumer<T, CAPACITY, A>
63where
64 T: bytemuck::AnyBitPattern,
65 A: Allocator,
66{
67 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 f.debug_struct("Consumer")
69 .field("read_cursor", &self.state.read_cursor)
70 .field("cached_write_cursor", &self.state.cached_write_cursor)
71 .finish_non_exhaustive()
72 }
73}
74
75// SAFETY: It is Send on its own but we need to forbid the Sync.
76unsafe impl<T, const CAPACITY: usize, A> Send for Consumer<T, CAPACITY, A>
77where
78 A: Allocator,
79 T: bytemuck::AnyBitPattern + Send,
80 A::Allocation<Slot<T>>: Send,
81{
82}
83
84// An ordinary `#[derive(Clone)]` adds additional requirements such as `A: Clone` which is
85// actually irrelevant as we only use [`Allocator::Allocation<T>`].
86impl<T, const CAPACITY: usize, A> Clone for Consumer<T, CAPACITY, A>
87where
88 T: bytemuck::AnyBitPattern,
89 A: Allocator,
90{
91 fn clone(&self) -> Self {
92 Self {
93 state: self.state.clone(),
94 inner: self.inner.clone(),
95 _not_sync: self._not_sync.clone(),
96 }
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use crate::mem::test_util::NeverAllocator;
104
105 static_assertions::assert_impl_all!(Consumer<u32, 2, NeverAllocator>: Send, Clone);
106 static_assertions::assert_not_impl_any!(Consumer<u32, 2, NeverAllocator>: Sync, Copy);
107}
108
109impl<T, const CAPACITY: usize, A> Consumer<T, CAPACITY, A>
110where
111 T: bytemuck::AnyBitPattern,
112 A: Allocator,
113{
114 pub(super) fn new(queue: Arc<Queue<T, CAPACITY, A>>) -> Self {
115 Self {
116 state: ConsumerStateInner {
117 read_cursor: 0,
118 cached_write_cursor: 0,
119 }
120 .into(),
121 inner: queue,
122 _not_sync: PhantomData,
123 }
124 }
125
126 /// Attempts to read the next value.
127 ///
128 /// Wait-free in the common case (value available, or queue empty);
129 /// lock-free when the producer is actively writing the target slot (the
130 /// consumer spin-loops until the producer publishes).
131 ///
132 /// # Protocol
133 ///
134 /// Each slot carries an even/odd seq number: even = stable, odd = mid
135 /// write. The consumer:
136 ///
137 /// 1. Loads the slot's seq. If odd, spin until it goes even.
138 /// 2. If the even seq is not the one expected for the current read
139 /// cursor, the producer has lapped us; reload the write cursor, jump
140 /// to the most recent slot, and return [`ReadResult::Lapped`].
141 /// 3. Otherwise read the slot's data, then re-load the seq. If the seq
142 /// is unchanged the read is committed and a [`ReadResult::Value`] is
143 /// returned; if the seq has moved, the read was torn and the loop
144 /// retries.
145 ///
146 /// # Examples
147 ///
148 /// ```
149 /// use low_latency_data_structures::spmc::{self, ReadResult, new};
150 /// use low_latency_data_structures::mem::global::GlobalAllocator;
151 ///
152 /// let (producer, mut consumer) = new::<u64, 16, GlobalAllocator>(
153 /// spmc::Options::global_mlocked(),
154 /// );
155 /// assert_eq!(consumer.try_read(), ReadResult::Empty);
156 /// producer.publish(7);
157 /// assert_eq!(consumer.try_read(), ReadResult::Value(7));
158 /// ```
159 #[inline]
160 pub fn try_read(&mut self) -> ReadResult<T> {
161 if self.state.read_cursor == self.state.cached_write_cursor {
162 // It still may not be empty
163 if self.is_still_empty() {
164 return ReadResult::Empty;
165 }
166 }
167 let slot = self.slot(self.state.read_cursor);
168 // That's the expected seq_no after producer has written the item
169 let expected_seq = self.state.read_cursor.wrapping_mul(2).wrapping_add(2);
170 loop {
171 let seq1 = slot.seq.load(Ordering::Acquire);
172 // If producer currently writes it, we wait (by the way, it means that we were overlapped)
173 if seq1 & 1 == 1 {
174 std::hint::spin_loop();
175 continue;
176 }
177 if seq1 != expected_seq {
178 // We were overlapped. Reload cached_write_cursor before jumping:
179 // under sustained producer overflow the cached value is itself
180 // stale (cached_write_cursor - 1 may have been lapped past
181 // again), and without the reload the consumer gets pinned in
182 // a Lapped -> Lapped cycle with skipped=0 forever.
183 self.state.cached_write_cursor = self
184 .inner
185 .producer_state
186 .write_cursor
187 .load(Ordering::Acquire);
188 let new_r_cursor = self.state.cached_write_cursor.wrapping_sub(1);
189 let skipped = new_r_cursor.wrapping_sub(self.state.read_cursor);
190 self.state.read_cursor = new_r_cursor;
191 return ReadResult::Lapped { skipped };
192 }
193 // SAFETY: T: AnyBitPattern, so even a torn read materialises a valid T;
194 // the seq2 check below rejects the value if the read raced a producer write.
195 let attempted_read = unsafe { slot.data.get().read_volatile() };
196 // ASM: prevent the seq1 load from moving below the read
197 fence(Ordering::Acquire);
198 let seq2 = slot.seq.load(Ordering::Relaxed);
199 if seq1 == seq2 {
200 self.state.read_cursor = self.state.read_cursor.wrapping_add(1);
201 return ReadResult::Value(attempted_read);
202 }
203 // Torn-read, retry
204 }
205 }
206
207 #[cold]
208 fn is_still_empty(&mut self) -> bool {
209 self.state.cached_write_cursor = self
210 .inner
211 .producer_state
212 .write_cursor
213 .load(Ordering::Acquire);
214 self.state.read_cursor == self.state.cached_write_cursor
215 }
216
217 /// Wraps the given `i` around `CAPACITY - 1`.
218 ///
219 /// # Safety
220 ///
221 /// - The masked index is always in `[0, CAPACITY)`, so the derived
222 /// pointer stays inside the allocation.
223 /// - The allocation is aligned and dereferenceable per the `Allocator`
224 /// contract.
225 /// - Every slot is initialized in [`new`](super::new) before the
226 /// `Queue` is wrapped in `Arc`, so `assume_init_ref` is sound.
227 /// - Returning `&Slot<T>` while another thread mutates through
228 /// `slot.data` / `slot.seq` is legal because the mutable state sits
229 /// behind `UnsafeCell` and atomics.
230 #[inline(always)]
231 fn slot(&self, i: usize) -> &Slot<T> {
232 unsafe {
233 self.inner
234 .slots
235 .ptr()
236 .wrapping_add(i & (CAPACITY - 1))
237 .as_ref_unchecked()
238 .get()
239 .as_ref_unchecked()
240 .assume_init_ref()
241 }
242 }
243}