use alloc::{boxed::Box, sync::Arc, vec::Vec};
use core::{
cell::UnsafeCell,
mem::MaybeUninit,
sync::atomic::{AtomicUsize, Ordering},
};
struct Slot<T>(UnsafeCell<MaybeUninit<T>>);
unsafe impl<T: Send> Send for Slot<T> {}
unsafe impl<T: Send> Sync for Slot<T> {}
impl<T> Slot<T> {
const fn uninit() -> Self {
Self(UnsafeCell::new(MaybeUninit::uninit()))
}
}
struct Ring<T> {
slots: Box<[Slot<T>]>,
head: AtomicUsize,
tail: AtomicUsize,
}
unsafe impl<T: Send> Send for Ring<T> {}
unsafe impl<T: Send> Sync for Ring<T> {}
impl<T> Ring<T> {
fn advance(&self, index: usize) -> usize {
let next = index + 1;
if next == self.slots.len() { 0 } else { next }
}
}
impl<T> Drop for Ring<T> {
fn drop(&mut self) {
let mut head = *self.head.get_mut();
let tail = *self.tail.get_mut();
while head != tail {
unsafe { (*self.slots[head].0.get()).assume_init_drop() };
head = self.advance(head);
}
}
}
pub(crate) struct Producer<T> {
ring: Arc<Ring<T>>,
}
impl<T> Producer<T> {
pub(crate) fn push(&mut self, item: T) -> Result<(), T> {
let tail = self.ring.tail.load(Ordering::Relaxed);
let next = self.ring.advance(tail);
if next == self.ring.head.load(Ordering::Acquire) {
return Err(item);
}
unsafe { (*self.ring.slots[tail].0.get()).write(item) };
self.ring.tail.store(next, Ordering::Release);
Ok(())
}
pub(crate) fn write_room(&self) -> usize {
let head = self.ring.head.load(Ordering::Acquire);
let tail = self.ring.tail.load(Ordering::Relaxed);
if tail >= head {
self.ring.slots.len() - (tail - head) - 1
} else {
head - tail - 1
}
}
}
pub(crate) struct Consumer<T> {
ring: Arc<Ring<T>>,
}
impl<T> Consumer<T> {
pub(crate) fn pop(&mut self) -> Option<T> {
let head = self.ring.head.load(Ordering::Relaxed);
if head == self.ring.tail.load(Ordering::Acquire) {
return None;
}
let item = unsafe { (*self.ring.slots[head].0.get()).assume_init_read() };
self.ring
.head
.store(self.ring.advance(head), Ordering::Release);
Some(item)
}
pub(crate) fn drain(&mut self, out: &mut [T]) -> usize {
let mut count = 0;
for slot in out {
let Some(item) = self.pop() else {
break;
};
*slot = item;
count += 1;
}
count
}
pub(crate) fn is_empty(&self) -> bool {
self.ring.head.load(Ordering::Relaxed) == self.ring.tail.load(Ordering::Acquire)
}
pub(crate) fn clear(&mut self) {
while self.pop().is_some() {}
}
}
pub(crate) fn channel<T>(capacity: usize) -> (Producer<T>, Consumer<T>) {
assert!(capacity > 0, "SPSC capacity must be non-zero");
let mut slots = Vec::with_capacity(capacity + 1);
slots.resize_with(capacity + 1, Slot::uninit);
let ring = Arc::new(Ring {
slots: slots.into_boxed_slice(),
head: AtomicUsize::new(0),
tail: AtomicUsize::new(0),
});
(Producer { ring: ring.clone() }, Consumer { ring })
}
#[cfg(test)]
mod tests {
extern crate std;
use std::thread;
use super::*;
#[test]
fn preserves_order_across_wraparound() {
let (mut producer, mut consumer) = channel(4);
for value in 0..4 {
producer.push(value).unwrap();
}
assert_eq!(producer.push(4), Err(4));
assert_eq!(consumer.pop(), Some(0));
assert_eq!(consumer.pop(), Some(1));
producer.push(4).unwrap();
producer.push(5).unwrap();
let mut out = [0; 4];
assert_eq!(consumer.drain(&mut out), 4);
assert_eq!(out, [2, 3, 4, 5]);
assert!(consumer.is_empty());
}
#[test]
fn producer_and_consumer_publish_concurrently() {
const COUNT: usize = 16_384;
let (mut producer, mut consumer) = channel(257);
let producer = thread::spawn(move || {
for value in 0..COUNT {
let mut pending = value;
loop {
match producer.push(pending) {
Ok(()) => break,
Err(value) => {
pending = value;
thread::yield_now();
}
}
}
}
});
for expected in 0..COUNT {
loop {
if let Some(value) = consumer.pop() {
assert_eq!(value, expected);
break;
}
thread::yield_now();
}
}
producer.join().unwrap();
assert!(consumer.is_empty());
}
#[test]
fn exposes_exact_usable_capacity() {
let (mut producer, _consumer) = channel(16_384);
for value in 0..16_384 {
producer.push(value).unwrap();
}
assert_eq!(producer.write_room(), 0);
assert_eq!(producer.push(16_384), Err(16_384));
}
}