#[cfg(feature = "alloc")]
use alloc::boxed::Box;
#[cfg(feature = "alloc")]
use super::CapacityError;
use core::mem::MaybeUninit;
#[cfg(not(feature = "loom"))]
use core::cell::UnsafeCell;
#[cfg(not(feature = "loom"))]
use core::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "loom")]
use loom::cell::UnsafeCell;
#[cfg(feature = "loom")]
use loom::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "loom")]
unsafe fn write_cell<T>(cell: &UnsafeCell<MaybeUninit<T>>, record: T) {
cell.with_mut(|ptr| unsafe { (*ptr).write(record) });
}
#[cfg(not(feature = "loom"))]
unsafe fn write_cell<T>(cell: &UnsafeCell<MaybeUninit<T>>, record: T) {
unsafe { (*cell.get()).write(record) };
}
#[cfg(feature = "loom")]
unsafe fn read_cell<T>(cell: &UnsafeCell<MaybeUninit<T>>) -> T {
cell.with(|ptr| unsafe { (*ptr).assume_init_read() })
}
#[cfg(not(feature = "loom"))]
unsafe fn read_cell<T>(cell: &UnsafeCell<MaybeUninit<T>>) -> T {
unsafe { (*cell.get()).assume_init_read() }
}
#[cfg(all(feature = "loom", feature = "alloc"))]
unsafe fn drop_cell<T>(cell: &UnsafeCell<MaybeUninit<T>>) {
cell.with_mut(|ptr| unsafe { (*ptr).assume_init_drop() });
}
#[cfg(all(not(feature = "loom"), feature = "alloc"))]
unsafe fn drop_cell<T>(cell: &UnsafeCell<MaybeUninit<T>>) {
unsafe { (*cell.get()).assume_init_drop() };
}
struct Cell<T> {
sequence: AtomicUsize,
data: UnsafeCell<MaybeUninit<T>>,
}
#[inline]
fn cells_push<T>(cells: &[Cell<T>], enqueue_pos: &AtomicUsize, record: T) -> Result<(), T> {
let mask = cells.len() - 1;
let mut pos = enqueue_pos.load(Ordering::Relaxed);
loop {
let cell = &cells[pos & mask];
let sequence = cell.sequence.load(Ordering::Acquire);
let diff = sequence.wrapping_sub(pos) as isize;
if diff == 0 {
match enqueue_pos.compare_exchange_weak(
pos,
pos.wrapping_add(1),
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => {
unsafe {
write_cell(&cell.data, record);
}
cell.sequence.store(pos.wrapping_add(1), Ordering::Release);
return Ok(());
}
Err(actual) => pos = actual,
}
} else if diff < 0 {
return Err(record);
} else {
pos = enqueue_pos.load(Ordering::Relaxed);
}
}
}
#[inline]
fn cells_dequeue<T>(cells: &[Cell<T>], dequeue_pos: &AtomicUsize) -> Option<T> {
let mask = cells.len() - 1;
let cap = cells.len();
let mut pos = dequeue_pos.load(Ordering::Relaxed);
loop {
let cell = &cells[pos & mask];
let sequence = cell.sequence.load(Ordering::Acquire);
let diff = sequence.wrapping_sub(pos.wrapping_add(1)) as isize;
if diff == 0 {
match dequeue_pos.compare_exchange_weak(
pos,
pos.wrapping_add(1),
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => {
let record = unsafe { read_cell(&cell.data) };
cell.sequence
.store(pos.wrapping_add(cap), Ordering::Release);
return Some(record);
}
Err(actual) => pos = actual,
}
} else if diff < 0 {
return None;
} else {
pos = dequeue_pos.load(Ordering::Relaxed);
}
}
}
#[cfg(feature = "alloc")]
pub struct Ring<T> {
buf: Box<[Cell<T>]>,
enqueue_pos: AtomicUsize,
dequeue_pos: AtomicUsize,
mask: usize,
cap: usize,
}
#[cfg(feature = "alloc")]
unsafe impl<T: Send> Send for Ring<T> {}
#[cfg(feature = "alloc")]
unsafe impl<T: Send> Sync for Ring<T> {}
#[cfg(feature = "alloc")]
impl<T> Ring<T> {
pub fn new(cap: usize) -> Result<Self, CapacityError> {
if cap == 0 {
return Err(CapacityError);
}
Ok(Self::build(cap))
}
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self::build(cap.max(1))
}
fn build(cap: usize) -> Self {
let cap = cap.next_power_of_two().max(2);
let buf = (0..cap)
.map(|index| Cell {
sequence: AtomicUsize::new(index),
data: UnsafeCell::new(MaybeUninit::uninit()),
})
.collect::<Box<[_]>>();
Self {
buf,
enqueue_pos: AtomicUsize::new(0),
dequeue_pos: AtomicUsize::new(0),
mask: cap - 1,
cap,
}
}
pub fn push(&self, record: T) -> Result<(), T> {
cells_push(&self.buf, &self.enqueue_pos, record)
}
pub fn dequeue(&self) -> Option<T> {
cells_dequeue(&self.buf, &self.dequeue_pos)
}
pub fn len(&self) -> usize {
let enqueue = self.enqueue_pos.load(Ordering::Acquire);
let dequeue = self.dequeue_pos.load(Ordering::Acquire);
enqueue.wrapping_sub(dequeue)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn cap(&self) -> usize {
self.cap
}
}
#[cfg(feature = "alloc")]
impl<T> Drop for Ring<T> {
fn drop(&mut self) {
let dequeue = self.dequeue_pos.load(Ordering::Relaxed);
let enqueue = self.enqueue_pos.load(Ordering::Relaxed);
let mut pos = dequeue;
while pos != enqueue {
let slot = pos & self.mask;
let published = self.buf[slot].sequence.load(Ordering::Relaxed) == pos.wrapping_add(1);
if published {
unsafe {
drop_cell(&self.buf[slot].data);
}
}
pos = pos.wrapping_add(1);
}
}
}
#[cfg(feature = "alloc")]
const PREFETCH_DISTANCE: usize = 16;
#[cfg(feature = "alloc")]
pub struct Drainer<'ring, T> {
ring: &'ring Ring<T>,
}
#[cfg(feature = "alloc")]
impl<'ring, T> Drainer<'ring, T> {
pub fn new(ring: &'ring Ring<T>) -> Self {
Self { ring }
}
pub fn drain_into(&mut self, out: &mut [T]) -> usize {
let mut count = 0;
while count < out.len() {
match self.ring.dequeue() {
Some(record) => {
let ahead = count + PREFETCH_DISTANCE;
if ahead < out.len() {
let slot = unsafe { out.as_ptr().add(ahead) };
crate::arch::prefetch_for_write(slot.cast());
}
out[count] = record;
count += 1;
}
None => break,
}
}
count
}
}
pub struct StaticRing<T, const N: usize> {
buf: [Cell<T>; N],
enqueue_pos: AtomicUsize,
dequeue_pos: AtomicUsize,
}
unsafe impl<T: Send, const N: usize> Send for StaticRing<T, N> {}
unsafe impl<T: Send, const N: usize> Sync for StaticRing<T, N> {}
impl<T, const N: usize> StaticRing<T, N> {
#[must_use]
#[cfg(not(feature = "loom"))]
pub const fn new() -> Self {
assert!(
N >= 2 && N.is_power_of_two(),
"StaticRing capacity N must be a power of two >= 2"
);
let mut buf = [const {
Cell {
sequence: AtomicUsize::new(0),
data: UnsafeCell::new(MaybeUninit::uninit()),
}
}; N];
let mut index = 0;
while index < N {
buf[index].sequence = AtomicUsize::new(index);
index += 1;
}
Self {
buf,
enqueue_pos: AtomicUsize::new(0),
dequeue_pos: AtomicUsize::new(0),
}
}
#[must_use]
#[cfg(feature = "loom")]
pub fn new() -> Self {
assert!(
N >= 2 && N.is_power_of_two(),
"StaticRing capacity N must be a power of two >= 2"
);
Self {
buf: core::array::from_fn(|index| Cell {
sequence: AtomicUsize::new(index),
data: UnsafeCell::new(MaybeUninit::uninit()),
}),
enqueue_pos: AtomicUsize::new(0),
dequeue_pos: AtomicUsize::new(0),
}
}
pub fn push(&self, record: T) -> Result<(), T> {
cells_push(&self.buf, &self.enqueue_pos, record)
}
pub fn dequeue(&self) -> Option<T> {
cells_dequeue(&self.buf, &self.dequeue_pos)
}
#[must_use]
pub fn len(&self) -> usize {
self.enqueue_pos
.load(Ordering::Acquire)
.wrapping_sub(self.dequeue_pos.load(Ordering::Acquire))
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub const fn capacity(&self) -> usize {
N
}
}
impl<T, const N: usize> Default for StaticRing<T, N> {
fn default() -> Self {
Self::new()
}
}
impl<T, const N: usize> Drop for StaticRing<T, N> {
fn drop(&mut self) {
while self.dequeue().is_some() {}
}
}
#[cfg(all(test, not(feature = "loom")))]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
extern crate std;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use super::StaticRing;
#[cfg(feature = "alloc")]
mod ring_tests {
use super::*;
use std::sync::Arc;
use std::thread;
use super::super::{Drainer, Ring};
use alloc::collections::VecDeque;
use alloc::vec;
use alloc::vec::Vec;
#[proxima::test]
#[case::push_all_drain_all(8)]
#[case::small_cap(2)]
#[case::large_cap(64)]
async fn happy_push_n_drain_n_in_order(#[case] cap: usize) {
let ring = Ring::new(cap).unwrap();
for item in 0..cap {
ring.push(item as u32).unwrap();
}
let mut out = vec![0u32; cap];
let mut drainer = Drainer::new(&ring);
let received = drainer.drain_into(&mut out);
assert_eq!(received, cap);
for (idx, value) in out.iter().enumerate() {
assert_eq!(*value, idx as u32);
}
}
#[proxima::test]
#[case::small(2)]
#[case::many(16)]
async fn sad_push_into_full_ring_returns_full(#[case] cap: usize) {
let ring = Ring::<u64>::new(cap).unwrap();
for item in 0..ring.cap() {
ring.push(item as u64).unwrap();
}
assert_eq!(ring.push(99), Err(99));
}
#[test]
fn worked_example_fifo_full_at_cap_then_reclaim() {
let ring = Ring::new(4).unwrap();
assert_eq!(ring.cap(), 4);
for value in 0..4u32 {
ring.push(value).unwrap();
}
assert_eq!(ring.push(99), Err(99));
let mut out = [0u32; 4];
let mut drainer = Drainer::new(&ring);
assert_eq!(drainer.drain_into(&mut out), 4);
assert_eq!(out, [0, 1, 2, 3]);
ring.push(7).unwrap();
let mut out2 = [0u32; 1];
assert_eq!(drainer.drain_into(&mut out2), 1);
assert_eq!(out2[0], 7);
}
#[test]
fn edge_min_cap_push_full_drain_push_again() {
let ring = Ring::new(2).unwrap();
ring.push(42u32).unwrap();
ring.push(43u32).unwrap();
assert_eq!(ring.push(99u32), Err(99));
let mut out = [0u32; 2];
let mut drainer = Drainer::new(&ring);
assert_eq!(drainer.drain_into(&mut out), 2);
assert_eq!(out, [42, 43]);
ring.push(7u32).unwrap();
let mut out2 = [0u32; 1];
assert_eq!(drainer.drain_into(&mut out2), 1);
assert_eq!(out2[0], 7);
}
#[test]
fn cap_rounds_up_to_power_of_two_min_two() {
assert_eq!(Ring::<u8>::new(1).unwrap().cap(), 2);
assert_eq!(Ring::<u8>::new(3).unwrap().cap(), 4);
assert_eq!(Ring::<u8>::new(1000).unwrap().cap(), 1024);
assert_eq!(Ring::<u8>::new(4096).unwrap().cap(), 4096);
}
#[test]
fn with_capacity_is_infallible_and_clamps_zero_to_min() {
assert_eq!(Ring::<u8>::with_capacity(0).cap(), 2);
assert_eq!(Ring::<u8>::with_capacity(1).cap(), 2);
assert_eq!(Ring::<u8>::with_capacity(3).cap(), 4);
let ring = Ring::with_capacity(1000);
assert_eq!(ring.cap(), 1024);
ring.push(7u32).unwrap();
assert_eq!(ring.dequeue(), Some(7));
}
#[test]
fn drop_items_inside_ring_are_dropped_properly() {
let counter = Arc::new(AtomicUsize::new(0));
struct Counted(Arc<AtomicUsize>);
impl Drop for Counted {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
{
let ring = Ring::new(4).unwrap();
assert!(ring.push(Counted(Arc::clone(&counter))).is_ok());
assert!(ring.push(Counted(Arc::clone(&counter))).is_ok());
}
assert_eq!(counter.load(Ordering::Relaxed), 2);
}
#[test]
fn concurrency_single_producer_order_preserved() {
const ITEMS: u32 = 10_000;
let ring = Arc::new(Ring::<u32>::new(512).unwrap());
let producer_ring = Arc::clone(&ring);
let producer = thread::spawn(move || {
let mut pushed = 0u32;
while pushed < ITEMS {
if producer_ring.push(pushed).is_ok() {
pushed += 1;
} else {
thread::yield_now();
}
}
});
let consumer_ring = Arc::clone(&ring);
let consumer = thread::spawn(move || {
let mut received = Vec::with_capacity(ITEMS as usize);
let mut buf = vec![0u32; 64];
let mut drainer = Drainer::new(&*consumer_ring);
while received.len() < ITEMS as usize {
let count = drainer.drain_into(&mut buf);
received.extend_from_slice(&buf[..count]);
if count == 0 {
thread::yield_now();
}
}
received
});
producer.join().expect("producer panicked");
let received = consumer.join().expect("consumer panicked");
assert_eq!(received.len(), ITEMS as usize);
for (idx, value) in received.iter().enumerate() {
assert_eq!(*value, idx as u32);
}
}
#[test]
fn mpsc_many_producers_no_loss_no_tear() {
#[derive(Clone, Copy)]
struct Tagged {
id: u64,
check: u64,
}
const MAGIC: u64 = 0x9E37_79B9_7F4A_7C15;
fn make(id: u64) -> Tagged {
Tagged {
id,
check: id ^ MAGIC,
}
}
const PRODUCERS: u64 = 8;
const PER_PRODUCER: u64 = 4_000;
const TOTAL: usize = (PRODUCERS * PER_PRODUCER) as usize;
for _round in 0..5 {
let ring = Arc::new(Ring::<Tagged>::new(16).unwrap());
let producers: Vec<_> = (0..PRODUCERS)
.map(|producer_id| {
let ring = Arc::clone(&ring);
thread::spawn(move || {
let base = producer_id * PER_PRODUCER;
let mut sent = 0u64;
while sent < PER_PRODUCER {
if ring.push(make(base + sent)).is_ok() {
sent += 1;
} else {
thread::yield_now();
}
}
})
})
.collect();
let consumer_ring = Arc::clone(&ring);
let consumer = thread::spawn(move || {
let mut seen: Vec<bool> = vec![false; TOTAL];
let mut received = 0usize;
let mut buf = vec![make(0); 32];
let mut drainer = Drainer::new(&*consumer_ring);
while received < TOTAL {
let count = drainer.drain_into(&mut buf);
if count == 0 {
thread::yield_now();
continue;
}
for tagged in buf.iter().take(count) {
assert_eq!(tagged.check, tagged.id ^ MAGIC, "torn record");
let id = tagged.id as usize;
assert!(!seen[id], "duplicate id {id}");
seen[id] = true;
received += 1;
}
}
seen
});
for producer in producers {
producer.join().expect("producer panicked");
}
let seen = consumer.join().expect("consumer panicked");
assert!(seen.into_iter().all(|hit| hit), "missing ids");
}
}
#[test]
fn worked_example_mpmc_two_consumers_fifo_then_reclaim() {
let ring = Ring::new(2).unwrap();
ring.push(b'A').unwrap();
ring.push(b'B').unwrap();
assert_eq!(
ring.push(b'C'),
Err(b'C'),
"full ring hands the record back"
);
let first = ring.dequeue().expect("first pop");
let second = ring.dequeue().expect("second pop");
assert_eq!([first, second], [b'A', b'B'], "FIFO order across consumers");
assert_eq!(ring.dequeue(), None, "empty after both consumed");
ring.push(b'D').unwrap();
assert_eq!(ring.dequeue(), Some(b'D'));
}
#[test]
fn mpmc_many_producers_many_consumers_no_loss_no_tear() {
#[derive(Clone, Copy)]
struct Tagged {
id: u64,
check: u64,
}
const MAGIC: u64 = 0x9E37_79B9_7F4A_7C15;
fn make(id: u64) -> Tagged {
Tagged {
id,
check: id ^ MAGIC,
}
}
const PRODUCERS: u64 = 8;
const CONSUMERS: usize = 4;
const PER_PRODUCER: u64 = 4_000;
const TOTAL: usize = (PRODUCERS * PER_PRODUCER) as usize;
for _round in 0..5 {
let ring = Arc::new(Ring::<Tagged>::new(16).unwrap());
let seen: Arc<Vec<AtomicUsize>> =
Arc::new((0..TOTAL).map(|_| AtomicUsize::new(0)).collect());
let received = Arc::new(AtomicUsize::new(0));
let producers: Vec<_> = (0..PRODUCERS)
.map(|producer_id| {
let ring = Arc::clone(&ring);
thread::spawn(move || {
let base = producer_id * PER_PRODUCER;
let mut sent = 0u64;
while sent < PER_PRODUCER {
if ring.push(make(base + sent)).is_ok() {
sent += 1;
} else {
thread::yield_now();
}
}
})
})
.collect();
let consumers: Vec<_> = (0..CONSUMERS)
.map(|_| {
let ring = Arc::clone(&ring);
let seen = Arc::clone(&seen);
let received = Arc::clone(&received);
thread::spawn(move || {
while received.load(Ordering::Relaxed) < TOTAL {
match ring.dequeue() {
Some(tagged) => {
assert_eq!(tagged.check, tagged.id ^ MAGIC, "torn record");
let prior = seen[tagged.id as usize]
.fetch_add(1, Ordering::Relaxed);
assert_eq!(prior, 0, "duplicate id {}", tagged.id);
received.fetch_add(1, Ordering::Relaxed);
}
None => thread::yield_now(),
}
}
})
})
.collect();
for producer in producers {
producer.join().expect("producer panicked");
}
for consumer in consumers {
consumer.join().expect("consumer panicked");
}
assert!(
seen.iter().all(|hit| hit.load(Ordering::Relaxed) == 1),
"every id consumed exactly once"
);
}
}
#[proxima::test]
#[case::tiny(4, 100)]
#[case::medium(32, 1_000)]
#[case::large(256, 10_000)]
async fn property_random_push_drain_interleaving_preserves_fifo(
#[case] cap: usize,
#[case] total_ops: usize,
) {
let ring = Ring::new(cap).unwrap();
let mut drainer = Drainer::new(&ring);
let mut expected_queue: VecDeque<u32> = VecDeque::new();
let mut next_push_value = 0u32;
let mut drain_buf = vec![0u32; cap];
for op_idx in 0..total_ops {
let do_push = op_idx % 3 != 2;
if do_push && expected_queue.len() < cap {
if ring.push(next_push_value).is_ok() {
expected_queue.push_back(next_push_value);
next_push_value = next_push_value.wrapping_add(1);
}
} else {
let count = drainer.drain_into(&mut drain_buf);
for drained in drain_buf.iter().take(count) {
let expected = expected_queue.pop_front().expect("queue underflow");
assert_eq!(*drained, expected);
}
}
}
}
#[test]
fn invalid_cap_zero_returns_error() {
let result = Ring::<u32>::new(0);
assert!(matches!(result, Err(crate::ring::CapacityError)));
}
}
#[test]
fn static_ring_backs_a_static_with_no_lazy_init() {
static RING: StaticRing<u32, 4> = StaticRing::new();
assert_eq!(RING.capacity(), 4);
for value in [11, 22, 33, 44] {
RING.push(value).unwrap();
}
assert_eq!(
RING.push(55),
Err(55),
"a const-built ring bounds like any other"
);
assert_eq!(RING.dequeue(), Some(11));
}
#[test]
fn static_ring_fifo_roundtrip() {
let ring = StaticRing::<u32, 4>::new();
assert_eq!(ring.capacity(), 4);
assert!(ring.is_empty());
for value in [10, 20, 30] {
ring.push(value).unwrap();
}
assert_eq!(ring.len(), 3);
assert_eq!(ring.dequeue(), Some(10));
assert_eq!(ring.dequeue(), Some(20));
assert_eq!(ring.dequeue(), Some(30));
assert_eq!(ring.dequeue(), None);
}
#[test]
fn static_ring_full_hands_record_back_without_blocking() {
let ring = StaticRing::<u32, 4>::new();
for value in [1, 2, 3, 4] {
ring.push(value).unwrap();
}
assert_eq!(ring.push(5), Err(5));
assert_eq!(ring.len(), 4);
}
#[test]
#[should_panic(expected = "power of two")]
fn static_ring_non_power_of_two_panics() {
let _ring = StaticRing::<u32, 6>::new();
}
#[test]
fn static_ring_drop_drains_buffered_records() {
struct Counted(Arc<AtomicUsize>);
impl Drop for Counted {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
let drops = Arc::new(AtomicUsize::new(0));
{
let ring = StaticRing::<Counted, 8>::new();
for _ in 0..3 {
assert!(ring.push(Counted(Arc::clone(&drops))).is_ok());
}
} assert_eq!(drops.load(Ordering::Relaxed), 3);
}
#[test]
fn static_ring_concurrent_mpmc_no_loss_no_dup() {
const PRODUCERS: usize = 4;
const PER: u32 = 10_000;
let total = PRODUCERS * PER as usize;
let ring = Arc::new(StaticRing::<u32, 1024>::new());
let seen = Arc::new(AtomicUsize::new(0));
let sum = Arc::new(AtomicUsize::new(0));
let mut handles = std::vec::Vec::new();
for producer in 0..PRODUCERS {
let ring = Arc::clone(&ring);
handles.push(thread::spawn(move || {
for index in 0..PER {
let value = producer as u32 * PER + index;
while ring.push(value).is_err() {
std::hint::spin_loop();
}
}
}));
}
for _ in 0..2 {
let ring = Arc::clone(&ring);
let seen = Arc::clone(&seen);
let sum = Arc::clone(&sum);
handles.push(thread::spawn(move || {
loop {
if let Some(value) = ring.dequeue() {
seen.fetch_add(1, Ordering::Relaxed);
sum.fetch_add(value as usize, Ordering::Relaxed);
} else if seen.load(Ordering::Relaxed) >= total {
break;
} else {
std::hint::spin_loop();
}
}
}));
}
for handle in handles {
handle.join().unwrap();
}
assert_eq!(seen.load(Ordering::Relaxed), total, "no item lost");
let expected: usize = (0..total).sum();
assert_eq!(
sum.load(Ordering::Relaxed),
expected,
"no duplicate or torn read"
);
}
}