use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::sync::atomic::{AtomicUsize, Ordering};
pub struct BoundedMpscQueue<T> {
mask: usize,
slots: Box<[Slot<T>]>,
head: AtomicUsize,
tail: AtomicUsize,
}
struct Slot<T> {
seq: AtomicUsize,
value: UnsafeCell<MaybeUninit<T>>,
}
unsafe impl<T: Send> Sync for BoundedMpscQueue<T> {}
unsafe impl<T: Send> Send for BoundedMpscQueue<T> {}
impl<T> BoundedMpscQueue<T> {
pub fn new(capacity: usize) -> Self {
let cap = capacity.next_power_of_two().max(2);
let mut slots = Vec::with_capacity(cap);
for i in 0..cap {
slots.push(Slot {
seq: AtomicUsize::new(i),
value: UnsafeCell::new(MaybeUninit::uninit()),
});
}
Self {
mask: cap - 1,
slots: slots.into_boxed_slice(),
head: AtomicUsize::new(0),
tail: AtomicUsize::new(0),
}
}
pub fn capacity(&self) -> usize {
self.mask + 1
}
pub fn producer_index(&self) -> usize {
self.tail.load(Ordering::Acquire)
}
pub fn consumer_index(&self) -> usize {
self.head.load(Ordering::Acquire)
}
pub fn try_enqueue(&self, value: T) -> Result<(), T> {
let mut tail = self.tail.load(Ordering::Relaxed);
loop {
let slot = &self.slots[tail & self.mask];
let seq = slot.seq.load(Ordering::Acquire);
let diff = seq.wrapping_sub(tail) as isize;
if diff == 0 {
match self.tail.compare_exchange_weak(
tail,
tail.wrapping_add(1),
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => {
unsafe { (*slot.value.get()).write(value) };
slot.seq.store(tail.wrapping_add(1), Ordering::Release);
return Ok(());
}
Err(t) => tail = t,
}
} else if diff < 0 {
return Err(value);
} else {
tail = self.tail.load(Ordering::Relaxed);
}
}
}
pub fn try_dequeue(&mut self) -> Option<T> {
let head = self.head.load(Ordering::Relaxed);
let slot = &self.slots[head & self.mask];
let seq = slot.seq.load(Ordering::Acquire);
let diff = seq.wrapping_sub(head.wrapping_add(1)) as isize;
if diff == 0 {
let value = unsafe { (*slot.value.get()).assume_init_read() };
slot.seq
.store(head.wrapping_add(self.mask + 1), Ordering::Release);
self.head.store(head.wrapping_add(1), Ordering::Release);
Some(value)
} else {
None
}
}
pub fn peek(&mut self) -> Option<&T> {
let head = self.head.load(Ordering::Relaxed);
let slot = &self.slots[head & self.mask];
let seq = slot.seq.load(Ordering::Acquire);
if seq.wrapping_sub(head.wrapping_add(1)) as isize == 0 {
Some(unsafe { (*slot.value.get()).assume_init_ref() })
} else {
None
}
}
pub fn clear(&mut self) -> usize {
let mut n = 0;
while self.try_dequeue().is_some() {
n += 1;
}
n
}
pub fn len(&self) -> usize {
let head = self.head.load(Ordering::Acquire);
let tail = self.tail.load(Ordering::Acquire);
tail.wrapping_sub(head)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn is_full(&self) -> bool {
self.len() >= self.capacity()
}
}
impl<T> Drop for BoundedMpscQueue<T> {
fn drop(&mut self) {
self.clear();
}
}
#[cfg(test)]
#[path = "bounded_tests.rs"]
mod tests;