low_latency_data_structures/spmc/consumer.rs
1use std::marker::PhantomData;
2use std::sync::Arc;
3use std::sync::atomic::{Ordering, fence};
4
5use crate::spmc::queue::Queue;
6
7#[repr(C, align(128))]
8pub(super) struct ConsumerState {
9 read_cursor: usize,
10 cached_write_cursor: usize,
11}
12
13/// Outcome of a [`Consumer::try_read`] call.
14///
15/// `try_read` is non-blocking and never allocates, so the return value
16/// distinguishes three cases the caller must handle: a successful read, an
17/// empty queue, or a detected lap. The enum is `#[must_use]` because
18/// silently dropping `Value` would lose the only copy of the data, and
19/// silently dropping `Lapped` would hide message loss.
20#[must_use = "the returned ReadResult indicates whether a value was read, the queue was empty, or the consumer was lapped"]
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum ReadResult<T> {
23 /// A value was successfully read from the next slot.
24 Value(T),
25 /// The queue is currently empty.
26 Empty,
27 /// The producer lapped this consumer at least once. The read cursor has
28 /// been advanced to the most recently published slot; `skipped` is the
29 /// number of values that were never observed.
30 Lapped {
31 /// Number of values skipped over by the lap-recovery jump.
32 skipped: usize,
33 },
34}
35
36/// A reading handle of an SPMC broadcast queue.
37///
38/// Each consumer has its own private read cursor: every consumer observes
39/// every value the producer publishes, independently, unless it falls behind
40/// by more than `CAPACITY` slots.
41///
42/// `Consumer` is [`Send`] but not [`Sync`]: at most one thread may call
43/// [`try_read`](Self::try_read) on a given consumer at a time. To get more
44/// consumers, request more at construction via [`new`](crate::spmc::new).
45pub struct Consumer<T: bytemuck::AnyBitPattern, const CAPACITY: usize> {
46 state: ConsumerState,
47 inner: Arc<Queue<T, CAPACITY>>,
48 _not_sync: PhantomData<*const ()>,
49}
50
51impl<T: bytemuck::AnyBitPattern, const CAPACITY: usize> std::fmt::Debug for Consumer<T, CAPACITY> {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 f.debug_struct("Consumer")
54 .field("read_cursor", &self.state.read_cursor)
55 .field("cached_write_cursor", &self.state.cached_write_cursor)
56 .finish_non_exhaustive()
57 }
58}
59
60// SAFETY: It is Send on its own but we need to forbid the Sync.
61unsafe impl<T: bytemuck::AnyBitPattern, const CAPACITY: usize> Send for Consumer<T, CAPACITY> {}
62
63static_assertions::assert_impl_all!(Consumer<u32, 2>: Send);
64static_assertions::assert_not_impl_any!(Consumer<u32, 2>: Sync, Clone, Copy);
65
66impl<T, const CAPACITY: usize> Consumer<T, CAPACITY>
67where
68 T: bytemuck::AnyBitPattern,
69{
70 pub(super) fn new(queue: Arc<Queue<T, CAPACITY>>) -> Self {
71 Self {
72 state: ConsumerState {
73 read_cursor: 0,
74 cached_write_cursor: 0,
75 },
76 inner: queue,
77 _not_sync: PhantomData,
78 }
79 }
80
81 /// Attempts to read the next value.
82 ///
83 /// Wait-free in the common case (value available, or queue empty);
84 /// lock-free when the producer is actively writing the target slot (the
85 /// consumer spin-loops until the producer publishes).
86 ///
87 /// # Protocol
88 ///
89 /// Each slot carries an even/odd seq number: even = stable, odd = mid
90 /// write. The consumer:
91 ///
92 /// 1. Loads the slot's seq. If odd, spin until it goes even.
93 /// 2. If the even seq is not the one expected for the current read
94 /// cursor, the producer has lapped us; reload the write cursor, jump
95 /// to the most recent slot, and return [`ReadResult::Lapped`].
96 /// 3. Otherwise read the slot's data, then re-load the seq. If the seq
97 /// is unchanged the read is committed and a [`ReadResult::Value`] is
98 /// returned; if the seq has moved, the read was torn and the loop
99 /// retries.
100 ///
101 /// # Examples
102 ///
103 /// ```
104 /// use low_latency_data_structures::spmc::{ReadResult, new};
105 ///
106 /// let (producer, [mut consumer]) = new::<u64, 16, 1>();
107 /// assert_eq!(consumer.try_read(), ReadResult::Empty);
108 /// producer.publish(7);
109 /// assert_eq!(consumer.try_read(), ReadResult::Value(7));
110 /// ```
111 #[inline]
112 pub fn try_read(&mut self) -> ReadResult<T> {
113 if self.state.read_cursor == self.state.cached_write_cursor {
114 // It still may not be empty
115 if self.is_still_empty() {
116 return ReadResult::Empty;
117 }
118 }
119 // SAFETY: it is guaranteed at compile-time that slots has exactly CAPACITY items
120 let slot = unsafe {
121 self.inner
122 .slots
123 .get_unchecked(self.state.read_cursor & (CAPACITY - 1))
124 };
125 // That's the expected seq_no after producer has written the item
126 let expected_seq = self.state.read_cursor.wrapping_mul(2).wrapping_add(2);
127 loop {
128 let seq1 = slot.seq.load(Ordering::Acquire);
129 // If producer currently writes it, we wait (by the way, it means that we were overlapped)
130 if seq1 & 1 == 1 {
131 std::hint::spin_loop();
132 continue;
133 }
134 if seq1 != expected_seq {
135 // We were overlapped. Reload cached_write_cursor before jumping:
136 // under sustained producer overflow the cached value is itself
137 // stale (cached_write_cursor - 1 may have been lapped past
138 // again), and without the reload the consumer gets pinned in
139 // a Lapped -> Lapped cycle with skipped=0 forever.
140 self.state.cached_write_cursor = self
141 .inner
142 .producer_state
143 .write_cursor
144 .load(Ordering::Acquire);
145 let new_r_cursor = self.state.cached_write_cursor.wrapping_sub(1);
146 let skipped = new_r_cursor.wrapping_sub(self.state.read_cursor);
147 self.state.read_cursor = new_r_cursor;
148 return ReadResult::Lapped { skipped };
149 }
150 // SAFETY: T: AnyBitPattern, so even a torn read materialises a valid T;
151 // the seq2 check below rejects the value if the read raced a producer write.
152 let attempted_read = unsafe { slot.data.get().read_volatile() };
153 // ASM: prevent the seq1 load from moving below the read
154 fence(Ordering::Acquire);
155 let seq2 = slot.seq.load(Ordering::Relaxed);
156 if seq1 == seq2 {
157 self.state.read_cursor = self.state.read_cursor.wrapping_add(1);
158 return ReadResult::Value(attempted_read);
159 }
160 // Torn-read, retry
161 }
162 }
163
164 #[cold]
165 fn is_still_empty(&mut self) -> bool {
166 self.state.cached_write_cursor = self
167 .inner
168 .producer_state
169 .write_cursor
170 .load(Ordering::Acquire);
171 self.state.read_cursor == self.state.cached_write_cursor
172 }
173}