use core::ptr::NonNull;
use core::sync::atomic::{AtomicU64, Ordering};
pub(crate) struct SpscRing {
head: NonNull<AtomicU64>,
tail: NonNull<AtomicU64>,
capacity: usize,
mask: u64,
}
unsafe impl Send for SpscRing {}
unsafe impl Sync for SpscRing {}
impl SpscRing {
pub(crate) unsafe fn new(
head: NonNull<AtomicU64>,
tail: NonNull<AtomicU64>,
capacity: usize,
) -> Self {
debug_assert!(
capacity.is_power_of_two(),
"capacity must be a power of two"
);
debug_assert!(capacity <= u64::MAX as usize / 2, "capacity too large");
Self {
head,
tail,
capacity,
mask: (capacity - 1) as u64,
}
}
fn head(&self) -> &AtomicU64 {
unsafe { self.head.as_ref() }
}
fn tail(&self) -> &AtomicU64 {
unsafe { self.tail.as_ref() }
}
pub(crate) fn try_push(&self) -> Option<usize> {
let head = self.head().load(Ordering::Relaxed);
let tail = self.tail().load(Ordering::Acquire);
if head.wrapping_sub(tail) == self.capacity as u64 {
return None;
}
Some((head & self.mask) as usize)
}
pub(crate) fn commit_push(&self) {
let head = self.head().load(Ordering::Relaxed);
self.head().store(head.wrapping_add(1), Ordering::Release);
}
pub(crate) fn try_pop(&self) -> Option<usize> {
let tail = self.tail().load(Ordering::Relaxed);
let head = self.head().load(Ordering::Acquire);
if tail == head {
return None;
}
Some((tail & self.mask) as usize)
}
pub(crate) fn commit_pop(&self) {
let tail = self.tail().load(Ordering::Relaxed);
self.tail().store(tail.wrapping_add(1), Ordering::Release);
}
#[allow(dead_code)]
pub(crate) fn capacity(&self) -> usize {
self.capacity
}
pub(crate) fn len(&self) -> usize {
let head = self.head().load(Ordering::Relaxed);
let tail = self.tail().load(Ordering::Relaxed);
head.wrapping_sub(tail) as usize
}
pub(crate) fn is_empty(&self) -> bool {
self.len() == 0
}
pub(crate) fn is_full(&self) -> bool {
self.len() == self.capacity
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::sync::atomic::AtomicU64;
fn make_ring(capacity: usize) -> (SpscRing, Box<AtomicU64>, Box<AtomicU64>) {
let head = Box::new(AtomicU64::new(0));
let tail = Box::new(AtomicU64::new(0));
let ring = unsafe {
SpscRing::new(
NonNull::from(head.as_ref()),
NonNull::from(tail.as_ref()),
capacity,
)
};
(ring, head, tail)
}
#[test]
fn empty_ring_returns_none_on_pop() {
let (ring, _h, _t) = make_ring(4);
assert!(ring.try_pop().is_none());
assert!(ring.is_empty());
}
#[test]
fn full_ring_returns_none_on_push() {
let (ring, _h, _t) = make_ring(4);
for _ in 0..4 {
let idx = ring.try_push().expect("should have space");
assert!(idx < 4);
ring.commit_push();
}
assert!(ring.is_full());
assert!(ring.try_push().is_none());
}
#[test]
fn push_pop_sequence_monotonic() {
let (ring, _h, _t) = make_ring(4);
let mut written = Vec::new();
let mut read = Vec::new();
for i in 0..4u64 {
let idx = ring.try_push().unwrap();
written.push(idx);
ring.commit_push();
assert_eq!(ring.len(), (i + 1) as usize);
}
for _ in 0..4 {
let idx = ring.try_pop().unwrap();
read.push(idx);
ring.commit_pop();
}
assert_eq!(written, read, "slot indices must match push order (FIFO)");
assert!(ring.is_empty());
}
#[test]
fn wrap_around() {
let (ring, _h, _t) = make_ring(4);
for _ in 0..8 {
let idx = ring.try_push().unwrap();
ring.commit_push();
let popped = ring.try_pop().unwrap();
ring.commit_pop();
assert_eq!(idx, popped);
}
}
#[test]
fn capacity_is_preserved() {
let (ring, _h, _t) = make_ring(8);
assert_eq!(ring.capacity(), 8);
}
#[test]
fn concurrent_spsc_no_loss_or_dup() {
use std::sync::Arc;
use std::thread;
const CAP: usize = 1024;
const N: u64 = 50_000;
let head: &'static AtomicU64 = Box::leak(Box::new(AtomicU64::new(0)));
let tail: &'static AtomicU64 = Box::leak(Box::new(AtomicU64::new(0)));
let slots: Arc<Vec<AtomicU64>> = Arc::new((0..CAP).map(|_| AtomicU64::new(0)).collect());
let slots_p = Arc::clone(&slots);
let producer = thread::spawn(move || {
let ring = unsafe { SpscRing::new(NonNull::from(head), NonNull::from(tail), CAP) };
for i in 0..N {
loop {
if let Some(idx) = ring.try_push() {
slots_p[idx].store(i, Ordering::Relaxed);
ring.commit_push();
break;
}
std::hint::spin_loop();
}
}
});
let slots_c = Arc::clone(&slots);
let consumer = thread::spawn(move || {
let ring = unsafe { SpscRing::new(NonNull::from(head), NonNull::from(tail), CAP) };
let mut got = Vec::with_capacity(N as usize);
while got.len() < N as usize {
if let Some(idx) = ring.try_pop() {
got.push(slots_c[idx].load(Ordering::Relaxed));
ring.commit_pop();
} else {
std::hint::spin_loop();
}
}
got
});
producer.join().unwrap();
let got = consumer.join().unwrap();
let expected: Vec<u64> = (0..N).collect();
assert_eq!(
got, expected,
"SPSC must deliver every message once in order"
);
}
}