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);
136
137 let mut head = self.cached_head.load(Ordering::Relaxed);
139 let mut current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
140
141 if current_len >= self.config.capacity {
142 head = self.head.load(Ordering::Acquire);
144 self.cached_head.store(head, Ordering::Relaxed);
145 current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
146
147 if current_len >= self.config.capacity {
148 self.overflow_count.fetch_add(1, Ordering::Relaxed);
149 return Err(value);
150 }
151 }
152
153 let next_tail = Self::increment(tail);
154
155 unsafe {
157 let buf = &mut *self.buffer.get();
158 buf[tail].write(value);
159
160 self.tail.store(next_tail, Ordering::Release);
162 }
163
164 Ok(())
165 }
166
167 pub fn pop(&self) -> Option<T> {
171 let head = self.head.load(Ordering::Relaxed);
173
174 let mut tail = self.cached_tail.load(Ordering::Relaxed);
176
177 if head == tail {
178 tail = self.tail.load(Ordering::Acquire);
180 self.cached_tail.store(tail, Ordering::Relaxed);
181
182 if head == tail {
183 return None;
184 }
185 }
186
187 let value = unsafe {
189 let buf = &mut *self.buffer.get();
190 let val = buf[head].assume_init_read();
191
192 self.head.store(Self::increment(head), Ordering::Release);
194
195 val
196 };
197
198 Some(value)
199 }
200
201 pub fn len(&self) -> usize {
203 let head = self.head.load(Ordering::Acquire);
204 let tail = self.tail.load(Ordering::Acquire);
205
206 tail.wrapping_sub(head) & (Self::CAPACITY - 1)
207 }
208
209 pub fn is_empty(&self) -> bool {
211 let head = self.head.load(Ordering::Relaxed);
212 let mut tail = self.cached_tail.load(Ordering::Relaxed);
213
214 if head == tail {
215 tail = self.tail.load(Ordering::Acquire);
216 self.cached_tail.store(tail, Ordering::Relaxed);
217 }
218
219 head == tail
220 }
221
222 pub fn capacity(&self) -> usize {
224 self.config.capacity
225 }
226
227 pub fn overflow_count(&self) -> usize {
229 self.overflow_count.load(Ordering::Relaxed)
230 }
231
232 pub fn empty_count(&self) -> usize {
234 self.config.capacity.saturating_sub(self.len())
235 }
236
237 pub fn should_backpressure(&self) -> bool {
239 let tail = self.tail.load(Ordering::Relaxed);
240 let mut head = self.cached_head.load(Ordering::Relaxed);
241 let mut len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
242
243 if len >= self.config.backpressure_threshold {
244 head = self.head.load(Ordering::Acquire);
245 self.cached_head.store(head, Ordering::Relaxed);
246 len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
247 }
248
249 len >= self.config.backpressure_threshold
250 }
251
252 pub fn write_available(&self) -> usize {
254 let tail = self.tail.load(Ordering::Relaxed);
255 let mut head = self.cached_head.load(Ordering::Relaxed);
256 let mut len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
257 if len >= self.config.capacity {
258 head = self.head.load(Ordering::Acquire);
259 self.cached_head.store(head, Ordering::Relaxed);
260 len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
261 }
262 self.config.capacity.saturating_sub(len)
263 }
264
265 pub fn read_available(&self) -> usize {
267 let head = self.head.load(Ordering::Relaxed);
268 let mut tail = self.cached_tail.load(Ordering::Relaxed);
269 if head == tail {
270 tail = self.tail.load(Ordering::Acquire);
271 self.cached_tail.store(tail, Ordering::Relaxed);
272 }
273 tail.wrapping_sub(head) & (Self::CAPACITY - 1)
274 }
275
276 pub fn wait_for_space(&self) {
281 let mut backoff = 1u32;
282 loop {
283 if !self.should_backpressure() {
284 return;
285 }
286 let limit = backoff.min(256);
288 for _ in 0..limit {
289 core::hint::spin_loop();
290 }
291 backoff = backoff.saturating_mul(2);
292 if backoff > 256 {
293 std::thread::yield_now();
294 backoff = 1;
295 }
296 }
297 }
298
299 pub fn wait_for_data(&self) {
304 let mut backoff = 1u32;
305 loop {
306 if !self.is_empty() {
307 return;
308 }
309 let limit = backoff.min(256);
310 for _ in 0..limit {
311 core::hint::spin_loop();
312 }
313 backoff = backoff.saturating_mul(2);
314 if backoff > 256 {
315 std::thread::yield_now();
316 backoff = 1;
317 }
318 }
319 }
320
321 pub fn push_batch(&self, slice: &[T]) -> usize
329 where
330 T: Copy,
331 {
332 let tail = self.tail.load(Ordering::Relaxed);
333 let mut head = self.cached_head.load(Ordering::Relaxed);
334 let mut current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
335
336 if current_len >= self.config.capacity {
337 head = self.head.load(Ordering::Acquire);
338 self.cached_head.store(head, Ordering::Relaxed);
339 current_len = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
340 if current_len >= self.config.capacity {
341 self.overflow_count
343 .fetch_add(slice.len(), Ordering::Relaxed);
344 return 0;
345 }
346 }
347
348 let available = self.config.capacity - current_len;
349 let count = available.min(slice.len());
350 if count == 0 {
351 return 0;
352 }
353 if count < slice.len() {
354 self.overflow_count
357 .fetch_add(slice.len() - count, Ordering::Relaxed);
358 }
359
360 let buf = unsafe { &mut *self.buffer.get() };
361 let mask = Self::CAPACITY - 1;
362 let start = tail & mask;
363
364 let buf_ptr = buf.as_mut_ptr() as *mut T;
367 if start + count <= Self::CAPACITY {
368 unsafe {
369 std::ptr::copy_nonoverlapping(slice.as_ptr(), buf_ptr.add(start), count);
370 }
371 } else {
372 let first_chunk = Self::CAPACITY - start;
373 let second_chunk = count - first_chunk;
374 unsafe {
375 std::ptr::copy_nonoverlapping(slice.as_ptr(), buf_ptr.add(start), first_chunk);
376 std::ptr::copy_nonoverlapping(
377 slice.as_ptr().add(first_chunk),
378 buf_ptr,
379 second_chunk,
380 );
381 }
382 }
383 let new_tail = tail.wrapping_add(count) & mask;
384 self.tail.store(new_tail, Ordering::Release);
385 count
386 }
387
388 pub fn pop_batch(&self, out: &mut [std::mem::MaybeUninit<T>]) -> usize {
394 let head = self.head.load(Ordering::Relaxed);
395 let mut tail = self.cached_tail.load(Ordering::Relaxed);
396
397 if head == tail {
398 tail = self.tail.load(Ordering::Acquire);
399 self.cached_tail.store(tail, Ordering::Relaxed);
400 if head == tail {
401 return 0;
402 }
403 }
404
405 let available = tail.wrapping_sub(head) & (Self::CAPACITY - 1);
406 let count = available.min(out.len());
407 if count == 0 {
408 return 0;
409 }
410
411 let buf = unsafe { &mut *self.buffer.get() };
412 let mask = Self::CAPACITY - 1;
413 let start = head & mask;
414
415 let buf_ptr = buf.as_ptr() as *const T;
418 let out_ptr = out.as_mut_ptr() as *mut T;
419 if start + count <= Self::CAPACITY {
420 unsafe {
421 std::ptr::copy_nonoverlapping(buf_ptr.add(start), out_ptr, count);
422 }
423 } else {
424 let first_chunk = Self::CAPACITY - start;
425 let second_chunk = count - first_chunk;
426 unsafe {
427 std::ptr::copy_nonoverlapping(buf_ptr.add(start), out_ptr, first_chunk);
428 std::ptr::copy_nonoverlapping(buf_ptr, out_ptr.add(first_chunk), second_chunk);
429 }
430 }
431 let new_head = head.wrapping_add(count) & mask;
432 self.head.store(new_head, Ordering::Release);
433 count
434 }
435
436 fn increment(index: usize) -> usize {
437 (index + 1) & (Self::CAPACITY - 1)
438 }
439}
440
441unsafe impl<T: Send> Send for SpscRingBuffer<T> {}
445unsafe impl<T: Send> Sync for SpscRingBuffer<T> {}
446
447impl<T> Drop for SpscRingBuffer<T> {
448 fn drop(&mut self) {
449 while self.pop().is_some() {}
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457 use std::sync::Arc;
458 use std::thread;
459
460 #[test]
461 fn supports_fifo_push_and_pop() {
462 let ring = SpscRingBuffer::<i32>::new(RingConfig::default());
463 assert!(ring.push(1).is_ok());
464 assert!(ring.push(2).is_ok());
465 assert_eq!(ring.len(), 2);
466 assert_eq!(ring.pop(), Some(1));
467 assert_eq!(ring.pop(), Some(2));
468 assert!(ring.is_empty());
469 }
470
471 #[test]
472 fn returns_full_when_capacity_is_reached() {
473 let config = RingConfig {
474 capacity: 2,
475 backpressure_threshold: 1,
476 };
477 let ring = SpscRingBuffer::<i32>::new(config);
478 assert!(ring.push(1).is_ok());
479 assert!(ring.push(2).is_ok());
480 assert_eq!(ring.push(3), Err(3));
481 }
482
483 #[test]
484 fn supports_single_producer_single_consumer_threads() {
485 let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
486 let producer_ring = Arc::clone(&ring);
487 let consumer_ring = Arc::clone(&ring);
488
489 let producer = thread::spawn(move || {
490 for value in 0..1_000 {
491 loop {
492 if producer_ring.push(value).is_ok() {
493 break;
494 }
495 std::thread::yield_now();
496 }
497 }
498 });
499
500 let consumer = thread::spawn(move || {
501 let mut values = Vec::new();
502 while values.len() < 1_000 {
503 if let Some(value) = consumer_ring.pop() {
504 values.push(value);
505 } else {
506 std::thread::yield_now();
507 }
508 }
509 values
510 });
511
512 producer.join().unwrap();
513 let values = consumer.join().unwrap();
514
515 assert_eq!(values.len(), 1_000);
516 assert_eq!(values.first(), Some(&0));
517 assert_eq!(values.last(), Some(&999));
518 }
519
520 #[test]
521 fn handles_wrap_around_correctly() {
522 let ring = SpscRingBuffer::<i32>::new(RingConfig::default());
523
524 for i in 0..1020 {
526 ring.push(i).unwrap();
527 }
528
529 assert_eq!(ring.len(), 1020);
530
531 for i in 0..1020 {
533 assert_eq!(ring.pop(), Some(i));
534 }
535
536 assert!(ring.is_empty());
537 }
538
539 #[test]
540 fn backpressure_threshold_works() {
541 let config = RingConfig {
542 capacity: 100,
543 backpressure_threshold: 70,
544 };
545 let ring = SpscRingBuffer::<i32>::new(config);
546
547 for i in 0..69 {
549 ring.push(i).unwrap();
550 }
551 assert!(!ring.should_backpressure());
552
553 let result = ring.push(70);
555 assert!(result.is_ok()); assert!(ring.should_backpressure());
557 }
558
559 #[test]
560 fn wait_for_space_releases() {
561 let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
562 let consumer_ring = Arc::clone(&ring);
563
564 for i in 0..1020 {
566 ring.push(i).unwrap();
567 }
568
569 consumer_ring.pop();
571
572 let result = ring.push(1021);
574 assert!(result.is_ok());
575 }
576
577 #[test]
578 fn wait_for_data_releases() {
579 let ring = Arc::new(SpscRingBuffer::<i32>::new(RingConfig::default()));
580 let producer_ring = Arc::clone(&ring);
581
582 producer_ring.push(42).unwrap();
586
587 assert_eq!(ring.pop(), Some(42));
589 }
590
591 #[test]
592 fn push_batch_writes_multiple_elements() {
593 let ring = SpscRingBuffer::<u64>::new(RingConfig::default());
594 let data: Vec<u64> = (0..100).collect();
595 let n = ring.push_batch(&data);
596 assert_eq!(n, 100);
597 assert_eq!(ring.len(), 100);
598 }
599
600 #[test]
601 fn push_batch_respects_capacity() {
602 let config = RingConfig {
603 capacity: 10,
604 backpressure_threshold: 8,
605 };
606 let ring = SpscRingBuffer::<u64>::new(config);
607 let data: Vec<u64> = (0..100).collect();
608 let n = ring.push_batch(&data);
609 assert_eq!(n, 10);
610 assert_eq!(ring.len(), 10);
611 assert_eq!(ring.overflow_count(), 90);
614 }
615
616 #[test]
617 fn pop_batch_reads_multiple_elements() {
618 let ring = SpscRingBuffer::<u64>::new(RingConfig::default());
619 for i in 0..50u64 {
620 ring.push(i).unwrap();
621 }
622 let mut out = vec![std::mem::MaybeUninit::uninit(); 20];
623 let n = ring.pop_batch(&mut out);
624 assert_eq!(n, 20);
625 let drained: Vec<u64> = out[..n]
626 .iter()
627 .map(|x| unsafe { x.assume_init_read() })
628 .collect();
629 assert_eq!(drained, (0..20).collect::<Vec<u64>>());
630 assert_eq!(ring.len(), 30);
631 }
632
633 #[test]
634 fn pop_batch_returns_available_count() {
635 let ring = SpscRingBuffer::<u64>::new(RingConfig::default());
636 for i in 0..5u64 {
637 ring.push(i).unwrap();
638 }
639 let mut out = vec![std::mem::MaybeUninit::uninit(); 100];
640 let n = ring.pop_batch(&mut out);
641 assert_eq!(n, 5);
642 }
643
644 #[test]
645 fn push_pop_batch_round_trip_spsc() {
646 let ring = Arc::new(SpscRingBuffer::<u64>::new(RingConfig::default()));
647 let prod = Arc::clone(&ring);
648 let cons = Arc::clone(&ring);
649 let iters = 5_000u64;
650 let batch_size = 64;
651
652 let h_prod = std::thread::spawn(move || {
653 let mut sent = 0u64;
654 while sent < iters {
655 let chunk_size = batch_size.min((iters - sent) as usize);
656 let chunk: Vec<u64> = (sent..sent + chunk_size as u64).collect();
657 let n = prod.push_batch(&chunk);
658 sent += n as u64;
659 if n == 0 {
660 prod.wait_for_space();
661 }
662 }
663 });
664
665 let h_cons = std::thread::spawn(move || {
666 let mut received = 0u64;
667 let mut buf = vec![std::mem::MaybeUninit::uninit(); batch_size];
668 while received < iters {
669 let n = cons.pop_batch(&mut buf);
670 if n > 0 {
671 received += n as u64;
672 } else {
673 cons.wait_for_data();
674 }
675 }
676 });
677
678 h_prod.join().unwrap();
679 h_cons.join().unwrap();
680 assert!(ring.is_empty());
681 }
682
683 #[test]
684 fn write_read_available_tracking() {
685 let config = RingConfig {
686 capacity: 100,
687 backpressure_threshold: 70,
688 };
689 let ring = SpscRingBuffer::<u64>::new(config);
690 assert_eq!(ring.write_available(), 100);
691 assert_eq!(ring.read_available(), 0);
692
693 for i in 0..30u64 {
694 ring.push(i).unwrap();
695 }
696 assert_eq!(ring.write_available(), 70);
697 assert_eq!(ring.read_available(), 30);
698
699 ring.pop();
703 assert_eq!(ring.read_available(), 29);
704 let wa = ring.write_available();
705 assert!(
706 wa == 70 || wa == 71,
707 "write_available should be 70 (conservative) or 71 (true): got {wa}"
708 );
709 }
710}