low_latency_data_structures/spmc/
queue.rs1use std::mem::MaybeUninit;
2use std::sync::Arc;
3use std::sync::atomic::{AtomicUsize, Ordering, fence};
4
5use crate::mem::{Allocation, Allocator};
6use crate::shim::cell::UnsafeCell;
7use crate::spmc::builder::Options;
8use crate::spmc::consumer::Consumer;
9use crate::spmc::producer::{Producer, ProducerState, ProducerStateInner};
10
11pub fn new<T, const CAPACITY: usize, A>(
55 options: Options<A>,
56) -> (Producer<T, CAPACITY, A>, Consumer<T, CAPACITY, A>)
57where
58 T: bytemuck::AnyBitPattern,
59 A: Allocator,
60{
61 const {
62 assert!(
63 CAPACITY.is_power_of_two(),
64 "Given capacity is not a power of two",
65 );
66 }
67 let slots = A::allocate::<Slot<T>>(CAPACITY, options.alloc);
68 for ix in 0..CAPACITY {
74 let slot = unsafe { slots.ptr().wrapping_add(ix).as_mut_unchecked() };
78 unsafe {
82 slot.get().write(MaybeUninit::new(Slot {
83 seq: AtomicUsize::new(0),
84 data: UnsafeCell::new(T::zeroed()),
85 }));
86 }
87 }
88 let q = Arc::new(Queue {
89 producer_state: ProducerStateInner {
90 write_cursor: AtomicUsize::new(0),
91 }
92 .into(),
93 slots,
94 });
95 let producer = Producer::new(q.clone());
96 let consumer = Consumer::new(q);
97 (producer, consumer)
98}
99
100pub struct Slot<T>
107where
108 T: bytemuck::AnyBitPattern,
109{
110 pub(super) seq: AtomicUsize,
111 pub(super) data: UnsafeCell<T>,
112}
113
114pub(super) struct Queue<T, const CAPACITY: usize, A>
115where
116 T: bytemuck::AnyBitPattern,
117 A: Allocator,
118{
119 pub(super) producer_state: ProducerState,
120 pub(super) slots: A::Allocation<Slot<T>>,
121}
122
123unsafe impl<T, const CAPACITY: usize, A> Sync for Queue<T, CAPACITY, A>
126where
127 T: bytemuck::AnyBitPattern,
128 A: Allocator,
129{
130}
131
132impl<T, const CAPACITY: usize, A> Queue<T, CAPACITY, A>
133where
134 T: bytemuck::AnyBitPattern,
135 A: Allocator,
136{
137 #[inline]
138 pub(super) fn publish(&self, value: T) {
139 let w_pos = self.producer_state.write_cursor.load(Ordering::Relaxed);
140 let seq_no = w_pos.wrapping_mul(2);
142 let slot = self.slot(w_pos);
143 slot.seq.store(seq_no.wrapping_add(1), Ordering::Relaxed);
144 fence(Ordering::Release);
146 unsafe { slot.data.get().write_volatile(value) };
147 slot.seq.store(seq_no.wrapping_add(2), Ordering::Release);
148 self.producer_state
149 .write_cursor
150 .store(w_pos.wrapping_add(1), Ordering::Release);
151 }
152
153 #[inline(always)]
167 fn slot(&self, i: usize) -> &Slot<T> {
168 unsafe {
169 self.slots
170 .ptr()
171 .wrapping_add(i & (CAPACITY - 1))
172 .as_ref_unchecked()
173 .get()
174 .as_ref_unchecked()
175 .assume_init_ref()
176 }
177 }
178}
179
180#[cfg(test)]
181#[cfg(feature = "tests_basic")]
182mod tests {
183 use super::*;
184 #[cfg(not(feature = "tests_hugepage"))]
185 use crate::mem::global::GlobalAllocator;
186 #[cfg(feature = "tests_hugepage")]
187 use crate::mem::hugepages::{HugepageAllocator, HugepageAllocatorOptions, HugepageSize};
188 use crate::mem::test_util::NeverAllocator;
189 use crate::spmc::consumer::ReadResult;
190
191 static_assertions::assert_impl_all!(Queue<u32, 1, NeverAllocator>: Sync, Send);
192
193 #[cfg(not(feature = "tests_hugepage"))]
194 fn spmc_options() -> Options<GlobalAllocator> {
195 Options::global_mlocked()
196 }
197 #[cfg(feature = "tests_hugepage")]
198 fn spmc_options() -> Options<HugepageAllocator> {
199 Options::builder()
200 .alloc(
201 HugepageAllocatorOptions::builder()
202 .mlock(true)
203 .hugepage_size(HugepageSize::H2MB)
204 .build(),
205 )
206 .build()
207 }
208
209 #[test]
210 fn single_thread_multiple_consumers_read_messages() {
211 const CAPACITY: usize = 128;
212 let (producer, mut c1) = new::<u64, CAPACITY, _>(spmc_options());
213 let mut c2 = c1.clone();
214 let mut c3 = c1.clone();
215 assert_eq!(c1.try_read(), ReadResult::Empty);
216 assert_eq!(c2.try_read(), ReadResult::Empty);
217 assert_eq!(c3.try_read(), ReadResult::Empty);
218
219 producer.publish(1);
220 assert_eq!(c1.try_read(), ReadResult::Value(1));
221 assert_eq!(c2.try_read(), ReadResult::Value(1));
222 assert_eq!(c3.try_read(), ReadResult::Value(1));
223 assert_eq!(c1.try_read(), ReadResult::Empty);
224 assert_eq!(c2.try_read(), ReadResult::Empty);
225 assert_eq!(c3.try_read(), ReadResult::Empty);
226
227 producer.publish(2);
228 assert_eq!(c1.try_read(), ReadResult::Value(2));
229 assert_eq!(c2.try_read(), ReadResult::Value(2));
230 assert_eq!(c1.try_read(), ReadResult::Empty);
231 assert_eq!(c2.try_read(), ReadResult::Empty);
232
233 producer.publish(3);
234 assert_eq!(c1.try_read(), ReadResult::Value(3));
235 assert_eq!(c1.try_read(), ReadResult::Empty);
236
237 assert_eq!(c2.try_read(), ReadResult::Value(3));
238 assert_eq!(c2.try_read(), ReadResult::Empty);
239
240 assert_eq!(c3.try_read(), ReadResult::Value(2));
241 assert_eq!(c3.try_read(), ReadResult::Value(3));
242 assert_eq!(c3.try_read(), ReadResult::Empty);
243 }
244
245 #[test]
246 fn concurrent_single_producer_multiple_consumers() {
247 use std::sync::Arc;
248 use std::sync::atomic::{AtomicBool, Ordering};
249 use std::thread;
250
251 const CAPACITY: usize = 1024;
252 const NCONSUMERS: usize = 4;
253 const N: u64 = 200_000;
254
255 let (producer, c0) = new::<u64, CAPACITY, _>(spmc_options());
256 let consumers: Vec<_> = (0..NCONSUMERS).map(|_| c0.clone()).collect();
257 drop(c0);
258 let done = Arc::new(AtomicBool::new(false));
259
260 let handles: Vec<_> = consumers
261 .into_iter()
262 .map(|mut c| {
263 let done = done.clone();
264 thread::spawn(move || -> u64 {
265 let mut last: Option<u64> = None;
266 let mut got = 0u64;
267 loop {
268 match c.try_read() {
269 ReadResult::Value(v) => {
270 if let Some(prev) = last {
271 assert!(v > prev);
272 }
273 last = Some(v);
274 got += 1;
275 }
276 ReadResult::Lapped { .. } => {}
277 ReadResult::Empty => {
278 if done.load(Ordering::Acquire) {
279 while let ReadResult::Value(v) = c.try_read() {
280 if let Some(prev) = last {
281 assert!(v > prev);
282 }
283 last = Some(v);
284 got += 1;
285 }
286 return got;
287 }
288 }
289 }
290 }
291 })
292 })
293 .collect();
294
295 for i in 0..N {
296 producer.publish(i);
297 }
298 done.store(true, Ordering::Release);
299
300 for h in handles {
301 let got = h.join().unwrap();
302 assert!(got > 0);
303 }
304 }
305
306 #[test]
307 fn consumer_can_be_overlapped_by_writer() {
308 const CAPACITY: usize = 8;
309 let (producer, mut c1) = new::<usize, CAPACITY, _>(spmc_options());
310 assert_eq!(c1.try_read(), ReadResult::Empty);
311
312 for i in 0..CAPACITY {
315 producer.publish(i);
316 }
317 assert_eq!(c1.try_read(), ReadResult::Value(0));
319 producer.publish(CAPACITY + 1);
321 producer.publish(CAPACITY + 2);
322 assert_eq!(c1.try_read(), ReadResult::Lapped { skipped: 8 });
325 assert_eq!(c1.try_read(), ReadResult::Value(CAPACITY + 2));
327 }
328}
329
330#[cfg(test)]
331#[cfg(feature = "tests_dhat")]
332mod tests_dhat {
333 use super::*;
334 use crate::mem::global::GlobalAllocator;
335
336 #[test]
337 fn hot_path_zero_allocations() {
338 let _profiler = dhat::Profiler::builder().testing().build();
339 let (producer, mut c1) = new::<u64, 1024, GlobalAllocator>(Options::global_mlocked());
340 let mut c2 = c1.clone();
341 let mut c3 = c1.clone();
342
343 for i in 0..1024u64 {
349 let _ = producer.publish(i);
350 }
351 for _ in 0..1024 {
352 let _ = c1.try_read();
353 let _ = c2.try_read();
354 let _ = c3.try_read();
355 }
356 let _ = dhat::HeapStats::get();
357
358 let before = dhat::HeapStats::get();
359
360 for i in 0..1024u64 {
361 let _ = producer.publish(i);
362 }
363 for _ in 0..1024 {
364 let _ = c1.try_read();
365 let _ = c2.try_read();
366 let _ = c3.try_read();
367 }
368
369 let after = dhat::HeapStats::get();
370 let allocs = after.total_blocks - before.total_blocks;
371 dhat::assert!(
372 allocs == 0,
373 "hot path allocated {} blocks; a real regression would be O(iters)",
374 allocs,
375 );
376 }
377}