#![doc = include_str!("README.md")]
use crossbeam_utils::CachePadded;
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::sync::atomic::{AtomicUsize, Ordering};
pub struct RingBuffer<T> {
buffer: Box<[UnsafeCell<MaybeUninit<T>>]>,
cap: usize,
head: CachePadded<AtomicUsize>,
tail: CachePadded<AtomicUsize>,
}
unsafe impl<T: Send> Sync for RingBuffer<T> {}
unsafe impl<T: Send> Send for RingBuffer<T> {}
impl<T> RingBuffer<T> {
pub fn new(capacity: usize) -> Self {
let cap = capacity.next_power_of_two();
let mut buffer = Vec::with_capacity(cap);
for _ in 0..cap {
buffer.push(UnsafeCell::new(MaybeUninit::uninit()));
}
let buffer = buffer.into_boxed_slice();
Self {
buffer,
cap,
head: CachePadded::new(AtomicUsize::new(0)),
tail: CachePadded::new(AtomicUsize::new(0)),
}
}
pub fn capacity(&self) -> usize {
self.cap
}
pub fn len(&self) -> usize {
let head = self.head.load(Ordering::Relaxed);
let tail = self.tail.load(Ordering::Relaxed);
head.wrapping_sub(tail)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn send(&self, item: T) -> Result<(), T> {
let head = self.head.load(Ordering::Relaxed);
let tail = self.tail.load(Ordering::Acquire);
if head.wrapping_sub(tail) == self.cap {
return Err(item);
}
let slot_idx = head & (self.cap - 1);
unsafe {
let slot_ptr = self.buffer[slot_idx].get();
(*slot_ptr).write(item);
}
self.head.store(head.wrapping_add(1), Ordering::Release);
Ok(())
}
pub fn recv(&self) -> Option<T> {
let tail = self.tail.load(Ordering::Relaxed);
let head = self.head.load(Ordering::Acquire);
if tail == head {
return None;
}
let slot_idx = tail & (self.cap - 1);
let item = unsafe {
let slot_ptr = self.buffer[slot_idx].get();
(*slot_ptr).assume_init_read()
};
self.tail.store(tail.wrapping_add(1), Ordering::Release);
Some(item)
}
}
impl<T> Drop for RingBuffer<T> {
fn drop(&mut self) {
let mut tail = self.tail.load(Ordering::Relaxed);
let head = self.head.load(Ordering::Relaxed);
while tail != head {
let slot_idx = tail & (self.cap - 1);
unsafe {
let slot_ptr = self.buffer[slot_idx].get();
std::ptr::drop_in_place((*slot_ptr).as_mut_ptr());
}
tail = tail.wrapping_add(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::thread;
#[test]
fn test_single_thread_send_recv() {
let rb = RingBuffer::new(4);
assert_eq!(rb.capacity(), 4);
rb.send("hello").unwrap();
rb.send("world").unwrap();
assert_eq!(rb.len(), 2);
assert_eq!(rb.recv(), Some("hello"));
assert_eq!(rb.recv(), Some("world"));
assert_eq!(rb.recv(), None);
assert_eq!(rb.len(), 0);
}
#[test]
fn test_full_and_empty() {
let rb = RingBuffer::new(2);
assert_eq!(rb.capacity(), 2);
rb.send(1).unwrap();
rb.send(2).unwrap();
assert_eq!(rb.send(3), Err(3));
assert_eq!(rb.len(), 2);
assert_eq!(rb.recv(), Some(1));
assert_eq!(rb.len(), 1);
rb.send(3).unwrap();
assert_eq!(rb.len(), 2);
assert_eq!(rb.recv(), Some(2));
assert_eq!(rb.recv(), Some(3));
assert_eq!(rb.recv(), None);
assert_eq!(rb.len(), 0);
}
#[test]
fn test_multi_thread_spsc() {
let rb = Arc::new(RingBuffer::new(1024));
let num_items = 1_000_000;
let producer_rb = rb.clone();
let producer_thread = thread::spawn(move || {
for i in 0..num_items {
while let Err(_item) = producer_rb.send(i) {
thread::yield_now();
}
}
});
let consumer_rb = rb.clone();
let consumer_thread = thread::spawn(move || {
let mut received_count = 0;
let mut next_expected = 0;
while received_count < num_items {
match consumer_rb.recv() {
Some(item) => {
assert_eq!(item, next_expected);
next_expected += 1;
received_count += 1;
}
None => {
thread::yield_now();
}
}
}
});
producer_thread.join().unwrap();
consumer_thread.join().unwrap();
}
#[test]
fn test_drop_cleanup() {
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug)]
struct Dropper;
impl Drop for Dropper {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
DROP_COUNT.store(0, Ordering::SeqCst);
{
let rb = RingBuffer::new(8);
rb.send(Dropper).unwrap();
rb.send(Dropper).unwrap();
rb.send(Dropper).unwrap();
{
let _d = rb.recv().unwrap();
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
}
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3);
}
}