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)]
39#[allow(dead_code)]
40fn prefetch_read<T>(ptr: *const T) {
41 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
42 unsafe {
43 std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_T0);
44 }
45 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
46 {
47 std::hint::black_box(ptr);
49 }
50}
51
52#[inline(always)]
54#[allow(dead_code)]
55fn prefetch_write<T>(ptr: *mut T) {
56 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
57 unsafe {
58 std::arch::x86_64::_mm_prefetch(ptr as *const i8, std::arch::x86_64::_MM_HINT_ET1);
59 }
60 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
61 {
62 std::hint::black_box(ptr);
63 }
64}
65
66#[derive(Debug)]
74pub struct SpscRingBuffer<T> {
75 buffer: UnsafeCell<[MaybeUninit<T>; RING_CAPACITY]>,
77
78 _pad1: [u8; CACHE_LINE],
80 head: AtomicUsize,
82 cached_tail: AtomicUsize,
84
85 _pad2: [u8; CACHE_LINE],
87 tail: AtomicUsize,
89 cached_head: AtomicUsize,
91
92 _pad3: [u8; CACHE_LINE],
94 overflow_count: AtomicUsize,
96 config: RingConfig,
98}
99
100impl<T> SpscRingBuffer<T> {
101 const CAPACITY: usize = RING_CAPACITY;
102
103 pub fn new(config: RingConfig) -> Self {
105 assert!(
106 config.capacity < Self::CAPACITY,
107 "capacity must be strictly less than RING_CAPACITY"
108 );
109
110 let buffer = UnsafeCell::new(unsafe {
112 MaybeUninit::<[MaybeUninit<T>; RING_CAPACITY]>::uninit().assume_init()
113 });
114
115 Self {
116 buffer,
117 _pad1: [0; CACHE_LINE],
118 head: AtomicUsize::new(0),
119 cached_tail: AtomicUsize::new(0),
120 _pad2: [0; CACHE_LINE],
121 tail: AtomicUsize::new(0),
122 cached_head: AtomicUsize::new(0),
123 _pad3: [0; CACHE_LINE],
124 overflow_count: AtomicUsize::new(0),
125 config,
126 }
127 }
128
129 pub fn push(&self, value: T) -> Result<(), T> {
134 let tail = self.tail.load(Ordering::Relaxed) & (Self::CAPACITY - 1);
138
139 let mut head = self.cached_head.load(Ordering::Relaxed);
141 let mut current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
142
143 if current_len >= self.config.capacity {
144 head = self.head.load(Ordering::Acquire);
146 self.cached_head.store(head, Ordering::Relaxed);
147 current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
148
149 if current_len >= self.config.capacity {
150 self.overflow_count.fetch_add(1, Ordering::Relaxed);
151 return Err(value);
152 }
153 }
154
155 let next_tail = (tail + 1) & (Self::CAPACITY - 1);
156
157 unsafe {
159 let buf = &mut *self.buffer.get();
160 buf.get_unchecked_mut(tail).write(value);
161
162 self.tail.store(next_tail, Ordering::Release);
164 }
165
166 Ok(())
167 }
168
169 pub fn pop(&self) -> Option<T> {
173 let head = self.head.load(Ordering::Relaxed) & (Self::CAPACITY - 1);
177
178 let mut tail = self.cached_tail.load(Ordering::Relaxed);
180
181 if head == tail {
182 tail = self.tail.load(Ordering::Acquire);
184 self.cached_tail.store(tail, Ordering::Relaxed);
185
186 if head == tail {
187 return None;
188 }
189 }
190
191 let value = unsafe {
193 let buf = &mut *self.buffer.get();
194 let val = buf.get_unchecked(head).assume_init_read();
195
196 self.head
198 .store((head + 1) & (Self::CAPACITY - 1), Ordering::Release);
199
200 val
201 };
202
203 Some(value)
204 }
205
206 pub fn len(&self) -> usize {
208 let head = self.head.load(Ordering::Acquire);
209 let tail = self.tail.load(Ordering::Acquire);
210
211 tail.wrapping_sub(head) & (Self::CAPACITY - 1)
212 }
213
214 pub fn is_empty(&self) -> bool {
216 let head = self.head.load(Ordering::Relaxed);
217 let mut tail = self.cached_tail.load(Ordering::Relaxed);
218
219 if head == tail {
220 tail = self.tail.load(Ordering::Acquire);
221 self.cached_tail.store(tail, Ordering::Relaxed);
222 }
223
224 head == tail
225 }
226
227 pub fn capacity(&self) -> usize {
229 self.config.capacity
230 }
231
232 pub fn overflow_count(&self) -> usize {
234 self.overflow_count.load(Ordering::Relaxed)
235 }
236
237 pub fn empty_count(&self) -> usize {
239 self.config.capacity.saturating_sub(self.len())
240 }
241
242 pub fn should_backpressure(&self) -> bool {
244 let tail = self.tail.load(Ordering::Relaxed);
245 let mut head = self.cached_head.load(Ordering::Relaxed);
246 let mut len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
247
248 if len >= self.config.backpressure_threshold {
249 head = self.head.load(Ordering::Acquire);
250 self.cached_head.store(head, Ordering::Relaxed);
251 len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
252 }
253
254 len >= self.config.backpressure_threshold
255 }
256
257 pub fn write_available(&self) -> usize {
259 let tail = self.tail.load(Ordering::Relaxed);
260 let mut head = self.cached_head.load(Ordering::Relaxed);
261 let mut len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
262 if len >= self.config.capacity {
263 head = self.head.load(Ordering::Acquire);
264 self.cached_head.store(head, Ordering::Relaxed);
265 len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
266 }
267 self.config.capacity.saturating_sub(len)
268 }
269
270 pub fn read_available(&self) -> usize {
272 let head = self.head.load(Ordering::Relaxed);
273 let mut tail = self.cached_tail.load(Ordering::Relaxed);
274 if head == tail {
275 tail = self.tail.load(Ordering::Acquire);
276 self.cached_tail.store(tail, Ordering::Relaxed);
277 }
278 tail.wrapping_sub(head) & (Self::CAPACITY - 1)
279 }
280
281 pub fn wait_for_space(&self) {
286 let mut backoff = 1u32;
287 loop {
288 if !self.should_backpressure() {
289 return;
290 }
291 let limit = backoff.min(256);
293 for _ in 0..limit {
294 core::hint::spin_loop();
295 }
296 backoff = backoff.saturating_mul(2);
297 if backoff > 256 {
298 std::thread::yield_now();
299 backoff = 1;
300 }
301 }
302 }
303
304 pub fn wait_for_data(&self) {
309 let mut backoff = 1u32;
310 loop {
311 if !self.is_empty() {
312 return;
313 }
314 let limit = backoff.min(256);
315 for _ in 0..limit {
316 core::hint::spin_loop();
317 }
318 backoff = backoff.saturating_mul(2);
319 if backoff > 256 {
320 std::thread::yield_now();
321 backoff = 1;
322 }
323 }
324 }
325
326 pub fn push_batch(&self, slice: &[T]) -> usize
334 where
335 T: Copy,
336 {
337 let tail = self.tail.load(Ordering::Relaxed);
338 let mut head = self.cached_head.load(Ordering::Relaxed);
339 let mut current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
340
341 if current_len >= self.config.capacity {
342 head = self.head.load(Ordering::Acquire);
343 self.cached_head.store(head, Ordering::Relaxed);
344 current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
345 if current_len >= self.config.capacity {
346 self.overflow_count
348 .fetch_add(slice.len(), Ordering::Relaxed);
349 return 0;
350 }
351 }
352
353 let available = self.config.capacity - current_len;
354 let count = available.min(slice.len());
355 if count == 0 {
356 return 0;
357 }
358 if count < slice.len() {
359 self.overflow_count
362 .fetch_add(slice.len() - count, Ordering::Relaxed);
363 }
364
365 let buf = unsafe { &mut *self.buffer.get() };
366 let mask = Self::CAPACITY - 1;
367 let start = tail & mask;
368
369 let buf_ptr = buf.as_mut_ptr() as *mut T;
372 if start + count <= Self::CAPACITY {
373 unsafe {
374 std::ptr::copy_nonoverlapping(slice.as_ptr(), buf_ptr.add(start), count);
375 }
376 } else {
377 let first_chunk = Self::CAPACITY - start;
378 let second_chunk = count - first_chunk;
379 unsafe {
380 std::ptr::copy_nonoverlapping(slice.as_ptr(), buf_ptr.add(start), first_chunk);
381 std::ptr::copy_nonoverlapping(
382 slice.as_ptr().add(first_chunk),
383 buf_ptr,
384 second_chunk,
385 );
386 }
387 }
388 let new_tail = tail.wrapping_add(count) & mask;
389 self.tail.store(new_tail, Ordering::Release);
390 count
391 }
392
393 pub fn pop_batch(&self, out: &mut [std::mem::MaybeUninit<T>]) -> usize {
399 let head = self.head.load(Ordering::Relaxed);
400 let mut tail = self.cached_tail.load(Ordering::Relaxed);
401
402 if head == tail {
403 tail = self.tail.load(Ordering::Acquire);
404 self.cached_tail.store(tail, Ordering::Relaxed);
405 if head == tail {
406 return 0;
407 }
408 }
409
410 let available = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
411 let count = available.min(out.len());
412 if count == 0 {
413 return 0;
414 }
415
416 let buf = unsafe { &mut *self.buffer.get() };
417 let mask = Self::CAPACITY - 1;
418 let start = head & mask;
419
420 let buf_ptr = buf.as_ptr() as *const T;
423 let out_ptr = out.as_mut_ptr() as *mut T;
424 if start + count <= Self::CAPACITY {
425 unsafe {
426 std::ptr::copy_nonoverlapping(buf_ptr.add(start), out_ptr, count);
427 }
428 } else {
429 let first_chunk = Self::CAPACITY - start;
430 let second_chunk = count - first_chunk;
431 unsafe {
432 std::ptr::copy_nonoverlapping(buf_ptr.add(start), out_ptr, first_chunk);
433 std::ptr::copy_nonoverlapping(buf_ptr, out_ptr.add(first_chunk), second_chunk);
434 }
435 }
436 let new_head = head.wrapping_add(count) & mask;
437 self.head.store(new_head, Ordering::Release);
438 count
439 }
440}
441
442unsafe impl<T: Send> Send for SpscRingBuffer<T> {}
446unsafe impl<T: Send> Sync for SpscRingBuffer<T> {}
447
448impl<T> Drop for SpscRingBuffer<T> {
449 fn drop(&mut self) {
450 while self.pop().is_some() {}
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use std::sync::Arc;
459 use std::thread;
460
461 #[test]
462 fn supports_fifo_push_and_pop() {
463 let ring = SpscRingBuffer::<i32>::new(RingConfig::default());
464 assert!(ring.push(1).is_ok());
465 assert!(ring.push(2).is_ok());
466 assert_eq!(ring.len(), 2);
467 assert_eq!(ring.pop(), Some(1));
468 assert_eq!(ring.pop(), Some(2));
469 assert!(ring.is_empty());
470 }
471
472 #[test]
473 fn returns_full_when_capacity_is_reached() {
474 let config = RingConfig {
475 capacity: 2,
476 backpressure_threshold: 1,
477 };
478 let ring = SpscRingBuffer::<i32>::new(config);
479 assert!(ring.push(1).is_ok());
480 assert!(ring.push(2).is_ok());
481 assert_eq!(ring.push(3), Err(3));
482 }
483
484 #[test]
485 fn supports_single_producer_single_consumer_threads() {
486 let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
487 let producer_ring = Arc::clone(&ring);
488 let consumer_ring = Arc::clone(&ring);
489
490 let producer = thread::spawn(move || {
491 for value in 0..1_000 {
492 loop {
493 if producer_ring.push(value).is_ok() {
494 break;
495 }
496 std::thread::yield_now();
497 }
498 }
499 });
500
501 let consumer = thread::spawn(move || {
502 let mut values = Vec::new();
503 while values.len() < 1_000 {
504 if let Some(value) = consumer_ring.pop() {
505 values.push(value);
506 } else {
507 std::thread::yield_now();
508 }
509 }
510 values
511 });
512
513 producer.join().unwrap();
514 let values = consumer.join().unwrap();
515
516 assert_eq!(values.len(), 1_000);
517 assert_eq!(values.first(), Some(&0));
518 assert_eq!(values.last(), Some(&999));
519 }
520
521 #[test]
522 fn handles_wrap_around_correctly() {
523 let ring = SpscRingBuffer::<i32>::new(RingConfig::default());
524
525 for i in 0..1020 {
527 ring.push(i).unwrap();
528 }
529
530 assert_eq!(ring.len(), 1020);
531
532 for i in 0..1020 {
534 assert_eq!(ring.pop(), Some(i));
535 }
536
537 assert!(ring.is_empty());
538 }
539
540 #[test]
541 fn backpressure_threshold_works() {
542 let config = RingConfig {
543 capacity: 100,
544 backpressure_threshold: 70,
545 };
546 let ring = SpscRingBuffer::<i32>::new(config);
547
548 for i in 0..69 {
550 ring.push(i).unwrap();
551 }
552 assert!(!ring.should_backpressure());
553
554 let result = ring.push(70);
556 assert!(result.is_ok()); assert!(ring.should_backpressure());
558 }
559
560 #[test]
561 fn wait_for_space_releases() {
562 let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
563 let consumer_ring = Arc::clone(&ring);
564
565 for i in 0..1020 {
567 ring.push(i).unwrap();
568 }
569
570 consumer_ring.pop();
572
573 let result = ring.push(1021);
575 assert!(result.is_ok());
576 }
577
578 #[test]
579 fn wait_for_data_releases() {
580 let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
581 let producer_ring = Arc::clone(&ring);
582
583 producer_ring.push(42).unwrap();
587
588 assert_eq!(ring.pop(), Some(42));
590 }
591
592 #[test]
593 fn push_batch_writes_multiple_elements() {
594 let ring = SpscRingBuffer::<u64>::new(RingConfig::default());
595 let data: Vec<u64> = (0..100).collect();
596 let n = ring.push_batch(&data);
597 assert_eq!(n, 100);
598 assert_eq!(ring.len(), 100);
599 }
600
601 #[test]
602 fn push_batch_respects_capacity() {
603 let config = RingConfig {
604 capacity: 10,
605 backpressure_threshold: 8,
606 };
607 let ring = SpscRingBuffer::<u64>::new(config);
608 let data: Vec<u64> = (0..100).collect();
609 let n = ring.push_batch(&data);
610 assert_eq!(n, 10);
611 assert_eq!(ring.len(), 10);
612 assert_eq!(ring.overflow_count(), 90);
615 }
616
617 #[test]
618 fn pop_batch_reads_multiple_elements() {
619 let ring = SpscRingBuffer::<u64>::new(RingConfig::default());
620 for i in 0..50u64 {
621 ring.push(i).unwrap();
622 }
623 let mut out = vec![std::mem::MaybeUninit::uninit(); 20];
624 let n = ring.pop_batch(&mut out);
625 assert_eq!(n, 20);
626 let drained: Vec<u64> = out[..n]
627 .iter()
628 .map(|x| unsafe { x.assume_init_read() })
629 .collect();
630 assert_eq!(drained, (0..20).collect::<Vec<u64>>());
631 assert_eq!(ring.len(), 30);
632 }
633
634 #[test]
635 fn pop_batch_returns_available_count() {
636 let ring = SpscRingBuffer::<u64>::new(RingConfig::default());
637 for i in 0..5u64 {
638 ring.push(i).unwrap();
639 }
640 let mut out = vec![std::mem::MaybeUninit::uninit(); 100];
641 let n = ring.pop_batch(&mut out);
642 assert_eq!(n, 5);
643 }
644
645 #[test]
646 fn push_pop_batch_round_trip_spsc() {
647 let ring = Arc::new(SpscRingBuffer::<u64>::new(RingConfig::default()));
648 let prod = Arc::clone(&ring);
649 let cons = Arc::clone(&ring);
650 let iters = 5_000u64;
651 let batch_size = 64;
652
653 let h_prod = std::thread::spawn(move || {
654 let mut sent = 0u64;
655 while sent < iters {
656 let chunk_size = batch_size.min((iters - sent) as usize);
657 let chunk: Vec<u64> = (sent..sent + chunk_size as u64).collect();
658 let n = prod.push_batch(&chunk);
659 sent += n as u64;
660 if n == 0 {
661 prod.wait_for_space();
662 }
663 }
664 });
665
666 let h_cons = std::thread::spawn(move || {
667 let mut received = 0u64;
668 let mut buf = vec![std::mem::MaybeUninit::uninit(); batch_size];
669 while received < iters {
670 let n = cons.pop_batch(&mut buf);
671 if n > 0 {
672 received += n as u64;
673 } else {
674 cons.wait_for_data();
675 }
676 }
677 });
678
679 h_prod.join().unwrap();
680 h_cons.join().unwrap();
681 assert!(ring.is_empty());
682 }
683
684 #[test]
685 fn write_read_available_tracking() {
686 let config = RingConfig {
687 capacity: 100,
688 backpressure_threshold: 70,
689 };
690 let ring = SpscRingBuffer::<u64>::new(config);
691 assert_eq!(ring.write_available(), 100);
692 assert_eq!(ring.read_available(), 0);
693
694 for i in 0..30u64 {
695 ring.push(i).unwrap();
696 }
697 assert_eq!(ring.write_available(), 70);
698 assert_eq!(ring.read_available(), 30);
699
700 ring.pop();
704 assert_eq!(ring.read_available(), 29);
705 let wa = ring.write_available();
706 assert!(
707 wa == 70 || wa == 71,
708 "write_available should be 70 (conservative) or 71 (true): got {wa}"
709 );
710 }
711}