#![warn(missing_docs)]
#![allow(unexpected_cfgs)]
mod sync {
#[cfg(loom)]
pub use loom::cell::UnsafeCell;
#[cfg(loom)]
pub use loom::sync::Arc;
#[cfg(loom)]
pub use loom::sync::atomic::{AtomicUsize, Ordering};
#[cfg(not(loom))]
pub use std::cell::UnsafeCell;
#[cfg(not(loom))]
pub use std::sync::Arc;
#[cfg(not(loom))]
pub use std::sync::atomic::{AtomicUsize, Ordering};
}
#[cfg(not(loom))]
trait UnsafeCellExt<T> {
fn with_mut<F, R>(&self, f: F) -> R
where
F: FnOnce(*mut T) -> R;
}
#[cfg(not(loom))]
impl<T> UnsafeCellExt<T> for std::cell::UnsafeCell<T> {
#[allow(clippy::inline_always)]
#[inline(always)]
fn with_mut<F, R>(&self, f: F) -> R
where
F: FnOnce(*mut T) -> R,
{
f(self.get())
}
}
use std::mem::MaybeUninit;
use sync::{Arc, AtomicUsize, Ordering, UnsafeCell};
#[repr(align(128))]
struct CachePadded<T>(T);
struct Ring<T> {
buf: Box<[UnsafeCell<MaybeUninit<T>>]>,
mask: usize,
head: CachePadded<AtomicUsize>,
tail: CachePadded<AtomicUsize>,
}
unsafe impl<T: Send> Send for Ring<T> {}
unsafe impl<T: Send> Sync for Ring<T> {}
impl<T> Ring<T> {
fn with_capacity(cap: usize) -> Self {
let cap = cap.max(2).next_power_of_two();
let mut v = Vec::with_capacity(cap);
for _ in 0..cap {
v.push(UnsafeCell::new(MaybeUninit::uninit()));
}
Ring {
buf: v.into_boxed_slice(),
mask: cap - 1,
head: CachePadded(AtomicUsize::new(0)),
tail: CachePadded(AtomicUsize::new(0)),
}
}
}
impl<T> Drop for Ring<T> {
fn drop(&mut self) {
let head = self.head.0.load(Ordering::Relaxed);
let tail = self.tail.0.load(Ordering::Relaxed);
let mut i = head;
while i != tail {
self.buf[i & self.mask].with_mut(|p| unsafe {
(*p).assume_init_drop();
});
i = i.wrapping_add(1);
}
}
}
pub struct Producer<T> {
inner: Arc<Ring<T>>,
head_cache: usize,
}
pub struct Consumer<T> {
inner: Arc<Ring<T>>,
tail_cache: usize,
}
pub fn ring<T>(capacity: usize) -> (Producer<T>, Consumer<T>) {
let r = Arc::new(Ring::with_capacity(capacity));
(
Producer {
inner: r.clone(),
head_cache: 0,
},
Consumer {
inner: r,
tail_cache: 0,
},
)
}
impl<T> Producer<T> {
pub fn push(&mut self, val: T) -> Result<(), T> {
let r = &*self.inner;
let tail = r.tail.0.load(Ordering::Relaxed);
if tail.wrapping_sub(self.head_cache) > r.mask {
self.head_cache = r.head.0.load(Ordering::Acquire);
if tail.wrapping_sub(self.head_cache) > r.mask {
return Err(val); }
}
r.buf[tail & r.mask].with_mut(|p| unsafe {
(*p).write(val);
});
r.tail.0.store(tail.wrapping_add(1), Ordering::Release);
Ok(())
}
pub fn is_full(&self) -> bool {
let r = &*self.inner;
let tail = r.tail.0.load(Ordering::Relaxed);
let head = r.head.0.load(Ordering::Acquire);
tail.wrapping_sub(head) > r.mask
}
pub fn capacity(&self) -> usize {
self.inner.mask + 1
}
}
impl<T> Consumer<T> {
pub fn pop(&mut self) -> Option<T> {
let r = &*self.inner;
let head = r.head.0.load(Ordering::Relaxed);
if head == self.tail_cache {
self.tail_cache = r.tail.0.load(Ordering::Acquire);
if head == self.tail_cache {
return None; }
}
let val = r.buf[head & r.mask].with_mut(|p| unsafe { (*p).assume_init_read() });
r.head.0.store(head.wrapping_add(1), Ordering::Release);
Some(val)
}
pub fn is_empty(&self) -> bool {
let r = &*self.inner;
let head = r.head.0.load(Ordering::Relaxed);
let tail = r.tail.0.load(Ordering::Acquire);
head == tail
}
pub fn len(&self) -> usize {
let r = &*self.inner;
let tail = r.tail.0.load(Ordering::Acquire);
let head = r.head.0.load(Ordering::Relaxed);
tail.wrapping_sub(head)
}
pub fn capacity(&self) -> usize {
self.inner.mask + 1
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn capacity_rounds_up_to_power_of_two() {
let (tx, rx) = ring::<u8>(3);
assert_eq!(tx.capacity(), 4);
assert_eq!(rx.capacity(), tx.capacity());
let (tx, _rx) = ring::<u8>(1);
assert_eq!(tx.capacity(), 2); let (tx, _rx) = ring::<u8>(1024);
assert_eq!(tx.capacity(), 1024);
}
#[test]
fn fifo_order_and_full_empty() {
let (mut tx, mut rx) = ring::<u32>(4); assert!(rx.is_empty());
for i in 0..4 {
assert!(tx.push(i).is_ok());
}
assert!(tx.is_full());
assert_eq!(tx.push(99), Err(99)); for i in 0..4 {
assert_eq!(rx.pop(), Some(i)); }
assert_eq!(rx.pop(), None);
assert!(rx.is_empty());
}
#[test]
fn wraps_around_many_times() {
let (mut tx, mut rx) = ring::<usize>(2);
for i in 0..10_000 {
assert!(tx.push(i).is_ok());
assert_eq!(rx.pop(), Some(i));
}
assert_eq!(rx.pop(), None);
}
#[test]
fn len_tracks_occupancy() {
let (mut tx, mut rx) = ring::<u8>(8);
assert_eq!(rx.len(), 0);
tx.push(1).unwrap();
tx.push(2).unwrap();
assert_eq!(rx.len(), 2);
rx.pop().unwrap();
assert_eq!(rx.len(), 1);
}
use std::sync::Arc as StdArc;
struct Bomb(StdArc<AtomicUsize>);
impl Drop for Bomb {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn drops_queued_elements_exactly_once() {
let dropped = StdArc::new(AtomicUsize::new(0));
{
let (mut tx, mut rx) = ring::<Bomb>(8);
for _ in 0..5 {
assert!(tx.push(Bomb(dropped.clone())).is_ok());
}
drop(rx.pop());
drop(rx.pop());
assert_eq!(dropped.load(Ordering::SeqCst), 2);
drop(tx);
drop(rx); }
assert_eq!(dropped.load(Ordering::SeqCst), 5);
}
#[test]
fn spsc_stress_across_threads() {
const N: u64 = if cfg!(miri) { 2_000 } else { 1_000_000 };
let (mut tx, mut rx) = ring::<u64>(64);
let producer = std::thread::spawn(move || {
for i in 0..N {
while tx.push(i).is_err() {
std::hint::spin_loop();
}
}
});
let mut next = 0u64;
while next < N {
match rx.pop() {
Some(v) => {
assert_eq!(v, next, "out-of-order or lost value");
next += 1;
}
None => std::hint::spin_loop(),
}
}
producer.join().unwrap();
assert_eq!(next, N);
}
#[test]
fn stress_with_intermittent_consumer() {
const N: u64 = if cfg!(miri) { 2_000 } else { 200_000 };
let (mut tx, mut rx) = ring::<u64>(16);
let done = Arc::new(AtomicBool::new(false));
let done_p = done.clone();
let producer = std::thread::spawn(move || {
for i in 0..N {
while tx.push(i).is_err() {
std::thread::yield_now();
}
}
done_p.store(true, Ordering::Release);
});
let mut next = 0u64;
let mut spins = 0u64;
loop {
if let Some(v) = rx.pop() {
assert_eq!(v, next);
next += 1;
spins += 1;
if spins.is_multiple_of(1000) {
std::thread::yield_now(); }
} else {
if done.load(Ordering::Acquire) && rx.is_empty() {
break;
}
std::thread::yield_now();
}
}
producer.join().unwrap();
assert_eq!(next, N);
}
}