1use std::cell::UnsafeCell;
12use std::mem::MaybeUninit;
13use std::sync::atomic::{AtomicUsize, Ordering};
14
15#[derive(Clone, Copy, Debug)]
17pub struct RingConfig {
18 pub capacity: usize,
20 pub backpressure_threshold: usize,
22}
23
24impl Default for RingConfig {
25 fn default() -> Self {
26 Self {
27 capacity: 4095, backpressure_threshold: 2867, }
30 }
31}
32
33const RING_CAPACITY: usize = 4096;
35const CACHE_LINE: usize = 128; #[inline(always)]
39fn prefetch_read<T>(ptr: *const T) {
40 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
41 unsafe {
42 std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_T0);
43 }
44 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
45 {
46 std::hint::black_box(ptr);
48 }
49}
50
51#[inline(always)]
53fn prefetch_write<T>(ptr: *mut T) {
54 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
55 unsafe {
56 std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_ET1);
57 }
58 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
59 {
60 std::hint::black_box(ptr);
61 }
62}
63
64#[derive(Debug)]
72pub struct SpscRingBuffer<T> {
73 buffer: UnsafeCell<[MaybeUninit<T>; RING_CAPACITY]>,
75
76 _pad1: [u8; CACHE_LINE],
78 head: AtomicUsize,
80 cached_tail: AtomicUsize,
82
83 _pad2: [u8; CACHE_LINE],
85 tail: AtomicUsize,
87 cached_head: AtomicUsize,
89
90 _pad3: [u8; CACHE_LINE],
92 overflow_count: AtomicUsize,
94 config: RingConfig,
96}
97
98impl<T> SpscRingBuffer<T> {
99 const CAPACITY: usize = RING_CAPACITY;
100
101 pub fn new(config: RingConfig) -> Self {
103 assert!(
104 config.capacity < Self::CAPACITY,
105 "capacity must be strictly less than RING_CAPACITY"
106 );
107
108 let buffer = UnsafeCell::new(unsafe {
110 MaybeUninit::<[MaybeUninit<T>; RING_CAPACITY]>::uninit().assume_init()
111 });
112
113 Self {
114 buffer,
115 _pad1: [0; CACHE_LINE],
116 head: AtomicUsize::new(0),
117 cached_tail: AtomicUsize::new(0),
118 _pad2: [0; CACHE_LINE],
119 tail: AtomicUsize::new(0),
120 cached_head: AtomicUsize::new(0),
121 _pad3: [0; CACHE_LINE],
122 overflow_count: AtomicUsize::new(0),
123 config,
124 }
125 }
126
127 pub fn push(&self, value: T) -> Result<(), T> {
132 let tail = self.tail.load(Ordering::Relaxed);
134
135 let mut head = self.cached_head.load(Ordering::Relaxed);
137 let mut current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
138
139 if current_len >= self.config.capacity {
140 head = self.head.load(Ordering::Acquire);
142 self.cached_head.store(head, Ordering::Relaxed);
143 current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
144
145 if current_len >= self.config.capacity {
146 self.overflow_count.fetch_add(1, Ordering::Relaxed);
147 return Err(value);
148 }
149 }
150
151 let next_tail = Self::increment(tail);
152
153 unsafe {
155 let buf = &mut *self.buffer.get();
156 buf[tail].write(value);
157
158 self.tail.store(next_tail, Ordering::Release);
160 }
161
162 Ok(())
163 }
164
165 pub fn pop(&self) -> Option<T> {
169 let head = self.head.load(Ordering::Relaxed);
171
172 let mut tail = self.cached_tail.load(Ordering::Relaxed);
174
175 if head == tail {
176 tail = self.tail.load(Ordering::Acquire);
178 self.cached_tail.store(tail, Ordering::Relaxed);
179
180 if head == tail {
181 return None;
182 }
183 }
184
185 let value = unsafe {
187 let buf = &mut *self.buffer.get();
188 let val = buf[head].assume_init_read();
189
190 self.head.store(Self::increment(head), Ordering::Release);
192
193 val
194 };
195
196 Some(value)
197 }
198
199 pub fn len(&self) -> usize {
201 let head = self.head.load(Ordering::Acquire);
202 let tail = self.tail.load(Ordering::Acquire);
203
204 tail.wrapping_sub(head) & (Self::CAPACITY - 1)
205 }
206
207 pub fn is_empty(&self) -> bool {
209 let head = self.head.load(Ordering::Relaxed);
210 let mut tail = self.cached_tail.load(Ordering::Relaxed);
211
212 if head == tail {
213 tail = self.tail.load(Ordering::Acquire);
214 self.cached_tail.store(tail, Ordering::Relaxed);
215 }
216
217 head == tail
218 }
219
220 pub fn capacity(&self) -> usize {
222 self.config.capacity
223 }
224
225 pub fn overflow_count(&self) -> usize {
227 self.overflow_count.load(Ordering::Relaxed)
228 }
229
230 pub fn empty_count(&self) -> usize {
232 self.config.capacity.saturating_sub(self.len())
233 }
234
235 pub fn should_backpressure(&self) -> bool {
237 let tail = self.tail.load(Ordering::Relaxed);
238 let mut head = self.cached_head.load(Ordering::Relaxed);
239 let mut len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
240
241 if len >= self.config.backpressure_threshold {
242 head = self.head.load(Ordering::Acquire);
243 self.cached_head.store(head, Ordering::Relaxed);
244 len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
245 }
246
247 len >= self.config.backpressure_threshold
248 }
249
250 pub fn write_available(&self) -> usize {
252 let tail = self.tail.load(Ordering::Relaxed);
253 let mut head = self.cached_head.load(Ordering::Relaxed);
254 let mut len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
255 if len >= self.config.capacity {
256 head = self.head.load(Ordering::Acquire);
257 self.cached_head.store(head, Ordering::Relaxed);
258 len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
259 }
260 self.config.capacity.saturating_sub(len)
261 }
262
263 pub fn read_available(&self) -> usize {
265 let head = self.head.load(Ordering::Relaxed);
266 let mut tail = self.cached_tail.load(Ordering::Relaxed);
267 if head == tail {
268 tail = self.tail.load(Ordering::Acquire);
269 self.cached_tail.store(tail, Ordering::Relaxed);
270 }
271 tail.wrapping_sub(head) & (Self::CAPACITY - 1)
272 }
273
274 pub fn wait_for_space(&self) {
279 let mut backoff = 1u32;
280 loop {
281 if !self.should_backpressure() {
282 return;
283 }
284 let limit = backoff.min(256);
286 for _ in 0..limit {
287 core::hint::spin_loop();
288 }
289 backoff = backoff.saturating_mul(2);
290 if backoff > 256 {
291 std::thread::yield_now();
292 backoff = 1;
293 }
294 }
295 }
296
297 pub fn wait_for_data(&self) {
302 let mut backoff = 1u32;
303 loop {
304 if !self.is_empty() {
305 return;
306 }
307 let limit = backoff.min(256);
308 for _ in 0..limit {
309 core::hint::spin_loop();
310 }
311 backoff = backoff.saturating_mul(2);
312 if backoff > 256 {
313 std::thread::yield_now();
314 backoff = 1;
315 }
316 }
317 }
318
319 pub fn push_batch(&self, slice: &[T]) -> usize
327 where
328 T: Copy,
329 {
330 let tail = self.tail.load(Ordering::Relaxed);
331 let mut head = self.cached_head.load(Ordering::Relaxed);
332 let mut current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
333
334 if current_len >= self.config.capacity {
335 head = self.head.load(Ordering::Acquire);
336 self.cached_head.store(head, Ordering::Relaxed);
337 current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
338 if current_len >= self.config.capacity {
339 self.overflow_count
341 .fetch_add(slice.len(), Ordering::Relaxed);
342 return 0;
343 }
344 }
345
346 let available = self.config.capacity - current_len;
347 let count = available.min(slice.len());
348 if count == 0 {
349 return 0;
350 }
351 if count < slice.len() {
352 self.overflow_count
355 .fetch_add(slice.len() - count, Ordering::Relaxed);
356 }
357
358 let buf = unsafe { &mut *self.buffer.get() };
359 let mask = Self::CAPACITY - 1;
360
361 prefetch_write(&mut buf[tail & mask] as *mut _);
363
364 for i in 0..count {
365 let idx = (tail + i) & mask;
366 if i % 16 == 14 && i + 2 < count {
368 prefetch_write(&mut buf[(tail + i + 2) & mask] as *mut _);
369 }
370 buf[idx].write(slice[i]);
371 }
372 let new_tail = tail.wrapping_add(count) & mask;
373 self.tail.store(new_tail, Ordering::Release);
374 count
375 }
376
377 pub fn pop_batch(&self, out: &mut [std::mem::MaybeUninit<T>]) -> usize {
383 let head = self.head.load(Ordering::Relaxed);
384 let mut tail = self.cached_tail.load(Ordering::Relaxed);
385
386 if head == tail {
387 tail = self.tail.load(Ordering::Acquire);
388 self.cached_tail.store(tail, Ordering::Relaxed);
389 if head == tail {
390 return 0;
391 }
392 }
393
394 let available = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
395 let count = available.min(out.len());
396 if count == 0 {
397 return 0;
398 }
399
400 let buf = unsafe { &mut *self.buffer.get() };
401 let mask = Self::CAPACITY - 1;
402
403 prefetch_read(&buf[head & mask] as *const _);
405
406 for (i, slot) in out.iter_mut().enumerate().take(count) {
407 let src = (head + i) & mask;
408 if i % 16 == 14 && i + 2 < count {
410 prefetch_read(&buf[(head + i + 2) & mask] as *const _);
411 }
412 *slot = std::mem::MaybeUninit::new(unsafe { buf[src].assume_init_read() });
413 }
414 let new_head = head.wrapping_add(count) & mask;
415 self.head.store(new_head, Ordering::Release);
416 count
417 }
418
419 fn increment(index: usize) -> usize {
420 (index + 1) & (Self::CAPACITY - 1)
421 }
422}
423
424unsafe impl<T: Send> Send for SpscRingBuffer<T> {}
428unsafe impl<T: Send> Sync for SpscRingBuffer<T> {}
429
430impl<T> Drop for SpscRingBuffer<T> {
431 fn drop(&mut self) {
432 while self.pop().is_some() {}
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440 use std::sync::Arc;
441 use std::thread;
442
443 #[test]
444 fn supports_fifo_push_and_pop() {
445 let ring = SpscRingBuffer::<i32>::new(RingConfig::default());
446 assert!(ring.push(1).is_ok());
447 assert!(ring.push(2).is_ok());
448 assert_eq!(ring.len(), 2);
449 assert_eq!(ring.pop(), Some(1));
450 assert_eq!(ring.pop(), Some(2));
451 assert!(ring.is_empty());
452 }
453
454 #[test]
455 fn returns_full_when_capacity_is_reached() {
456 let config = RingConfig {
457 capacity: 2,
458 backpressure_threshold: 1,
459 };
460 let ring = SpscRingBuffer::<i32>::new(config);
461 assert!(ring.push(1).is_ok());
462 assert!(ring.push(2).is_ok());
463 assert_eq!(ring.push(3), Err(3));
464 }
465
466 #[test]
467 fn supports_single_producer_single_consumer_threads() {
468 let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
469 let producer_ring = Arc::clone(&ring);
470 let consumer_ring = Arc::clone(&ring);
471
472 let producer = thread::spawn(move || {
473 for value in 0..1_000 {
474 loop {
475 if producer_ring.push(value).is_ok() {
476 break;
477 }
478 std::thread::yield_now();
479 }
480 }
481 });
482
483 let consumer = thread::spawn(move || {
484 let mut values = Vec::new();
485 while values.len() < 1_000 {
486 if let Some(value) = consumer_ring.pop() {
487 values.push(value);
488 } else {
489 std::thread::yield_now();
490 }
491 }
492 values
493 });
494
495 producer.join().unwrap();
496 let values = consumer.join().unwrap();
497
498 assert_eq!(values.len(), 1_000);
499 assert_eq!(values.first(), Some(&0));
500 assert_eq!(values.last(), Some(&999));
501 }
502
503 #[test]
504 fn handles_wrap_around_correctly() {
505 let ring = SpscRingBuffer::<i32>::new(RingConfig::default());
506
507 for i in 0..1020 {
509 ring.push(i).unwrap();
510 }
511
512 assert_eq!(ring.len(), 1020);
513
514 for i in 0..1020 {
516 assert_eq!(ring.pop(), Some(i));
517 }
518
519 assert!(ring.is_empty());
520 }
521
522 #[test]
523 fn backpressure_threshold_works() {
524 let config = RingConfig {
525 capacity: 100,
526 backpressure_threshold: 70,
527 };
528 let ring = SpscRingBuffer::<i32>::new(config);
529
530 for i in 0..69 {
532 ring.push(i).unwrap();
533 }
534 assert!(!ring.should_backpressure());
535
536 let result = ring.push(70);
538 assert!(result.is_ok()); assert!(ring.should_backpressure());
540 }
541
542 #[test]
543 fn wait_for_space_releases() {
544 let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
545 let consumer_ring = Arc::clone(&ring);
546
547 for i in 0..1020 {
549 ring.push(i).unwrap();
550 }
551
552 consumer_ring.pop();
554
555 let result = ring.push(1021);
557 assert!(result.is_ok());
558 }
559
560 #[test]
561 fn wait_for_data_releases() {
562 let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
563 let producer_ring = Arc::clone(&ring);
564
565 producer_ring.push(42).unwrap();
569
570 assert_eq!(ring.pop(), Some(42));
572 }
573
574 #[test]
575 fn push_batch_writes_multiple_elements() {
576 let ring = SpscRingBuffer::<u64>::new(RingConfig::default());
577 let data: Vec<u64> = (0..100).collect();
578 let n = ring.push_batch(&data);
579 assert_eq!(n, 100);
580 assert_eq!(ring.len(), 100);
581 }
582
583 #[test]
584 fn push_batch_respects_capacity() {
585 let config = RingConfig {
586 capacity: 10,
587 backpressure_threshold: 8,
588 };
589 let ring = SpscRingBuffer::<u64>::new(config);
590 let data: Vec<u64> = (0..100).collect();
591 let n = ring.push_batch(&data);
592 assert_eq!(n, 10);
593 assert_eq!(ring.len(), 10);
594 assert_eq!(ring.overflow_count(), 90);
597 }
598
599 #[test]
600 fn pop_batch_reads_multiple_elements() {
601 let ring = SpscRingBuffer::<u64>::new(RingConfig::default());
602 for i in 0..50u64 {
603 ring.push(i).unwrap();
604 }
605 let mut out = vec![std::mem::MaybeUninit::uninit(); 20];
606 let n = ring.pop_batch(&mut out);
607 assert_eq!(n, 20);
608 let drained: Vec<u64> = out[..n]
609 .iter()
610 .map(|x| unsafe { x.assume_init_read() })
611 .collect();
612 assert_eq!(drained, (0..20).collect::<Vec<u64>>());
613 assert_eq!(ring.len(), 30);
614 }
615
616 #[test]
617 fn pop_batch_returns_available_count() {
618 let ring = SpscRingBuffer::<u64>::new(RingConfig::default());
619 for i in 0..5u64 {
620 ring.push(i).unwrap();
621 }
622 let mut out = vec![std::mem::MaybeUninit::uninit(); 100];
623 let n = ring.pop_batch(&mut out);
624 assert_eq!(n, 5);
625 }
626
627 #[test]
628 fn push_pop_batch_round_trip_spsc() {
629 let ring = Arc::new(SpscRingBuffer::<u64>::new(RingConfig::default()));
630 let prod = Arc::clone(&ring);
631 let cons = Arc::clone(&ring);
632 let iters = 5_000u64;
633 let batch_size = 64;
634
635 let h_prod = std::thread::spawn(move || {
636 let mut sent = 0u64;
637 while sent < iters {
638 let chunk_size = batch_size.min((iters - sent) as usize);
639 let chunk: Vec<u64> = (sent..sent + chunk_size as u64).collect();
640 let n = prod.push_batch(&chunk);
641 sent += n as u64;
642 if n == 0 {
643 prod.wait_for_space();
644 }
645 }
646 });
647
648 let h_cons = std::thread::spawn(move || {
649 let mut received = 0u64;
650 let mut buf = vec![std::mem::MaybeUninit::uninit(); batch_size];
651 while received < iters {
652 let n = cons.pop_batch(&mut buf);
653 if n > 0 {
654 received += n as u64;
655 } else {
656 cons.wait_for_data();
657 }
658 }
659 });
660
661 h_prod.join().unwrap();
662 h_cons.join().unwrap();
663 assert!(ring.is_empty());
664 }
665
666 #[test]
667 fn write_read_available_tracking() {
668 let config = RingConfig {
669 capacity: 100,
670 backpressure_threshold: 70,
671 };
672 let ring = SpscRingBuffer::<u64>::new(config);
673 assert_eq!(ring.write_available(), 100);
674 assert_eq!(ring.read_available(), 0);
675
676 for i in 0..30u64 {
677 ring.push(i).unwrap();
678 }
679 assert_eq!(ring.write_available(), 70);
680 assert_eq!(ring.read_available(), 30);
681
682 ring.pop();
686 assert_eq!(ring.read_available(), 29);
687 let wa = ring.write_available();
688 assert!(
689 wa == 70 || wa == 71,
690 "write_available should be 70 (conservative) or 71 (true): got {wa}"
691 );
692 }
693}