use core::cell::UnsafeCell;
use core::cmp::Ordering as CmpOrdering;
use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::cache::CacheAligned;
#[cfg(feature = "std")]
use std::boxed::Box;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
const DEFAULT_QUEUE_CAPACITY: usize = 65536;
const SPINS_BEFORE_YIELD: u32 = 64;
#[cfg(test)]
pub(super) static REOPEN_WAITS: AtomicUsize = AtomicUsize::new(0);
pub(super) struct Slot<T> {
sequence: AtomicUsize,
data: UnsafeCell<MaybeUninit<T>>,
}
pub struct LockFreeQueue<T> {
buffer: Box<[Slot<T>]>,
mask: usize,
ring_len: usize,
capacity: usize,
head: CacheAligned<AtomicUsize>,
tail: CacheAligned<AtomicUsize>,
}
unsafe impl<T: Send> Send for LockFreeQueue<T> {}
unsafe impl<T: Send> Sync for LockFreeQueue<T> {}
impl<T> LockFreeQueue<T> {
pub fn new() -> Self {
Self::with_capacity(DEFAULT_QUEUE_CAPACITY)
}
#[track_caller]
pub fn with_capacity(capacity: usize) -> Self {
let capacity = capacity.max(1);
let ring_len = capacity.next_power_of_two().max(2);
#[cfg(feature = "std")]
let buffer: Box<[Slot<T>]> = (0..ring_len)
.map(|i| Slot {
sequence: AtomicUsize::new(i),
data: UnsafeCell::new(MaybeUninit::uninit()),
})
.collect::<std::vec::Vec<_>>()
.into_boxed_slice();
#[cfg(not(feature = "std"))]
let buffer: Box<[Slot<T>]> = (0..ring_len)
.map(|i| Slot {
sequence: AtomicUsize::new(i),
data: UnsafeCell::new(MaybeUninit::uninit()),
})
.collect::<alloc::vec::Vec<_>>()
.into_boxed_slice();
Self {
buffer,
mask: ring_len - 1,
ring_len,
capacity,
head: CacheAligned::new(AtomicUsize::new(0)),
tail: CacheAligned::new(AtomicUsize::new(0)),
}
}
#[inline]
pub fn try_enqueue(&self, item: T) -> Result<(), T> {
let mut pos = self.tail.load(Ordering::Relaxed);
let mut waits = 0;
loop {
let slot = &self.buffer[pos & self.mask];
let seq = slot.sequence.load(Ordering::Acquire);
#[allow(clippy::cast_possible_wrap)]
let diff = seq.wrapping_sub(pos) as isize;
match diff.cmp(&0) {
CmpOrdering::Equal => {
if self.holds_capacity(pos) {
return Err(item);
}
match self.tail.compare_exchange_weak(
pos,
pos.wrapping_add(1),
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => {
unsafe {
(*slot.data.get()).write(item);
}
slot.sequence.store(pos.wrapping_add(1), Ordering::Release);
return Ok(());
}
Err(actual) => pos = actual,
}
}
CmpOrdering::Less => {
if self.holds_capacity(pos) {
return Err(item);
}
wait_for_reopen(&mut waits);
pos = self.tail.load(Ordering::Relaxed);
}
CmpOrdering::Greater => pos = self.tail.load(Ordering::Relaxed),
}
}
}
#[inline]
pub(super) fn holds_capacity(&self, pos: usize) -> bool {
let queued = pos.wrapping_sub(self.head.load(Ordering::Acquire));
(self.capacity..=self.ring_len).contains(&queued)
}
#[inline]
pub fn enqueue(&self, item: T) {
let mut backoff: usize = 1;
let mut item = Some(item);
loop {
match self.try_enqueue(item.take().expect("invariant: item present")) {
Ok(()) => return,
Err(returned) => {
item = Some(returned);
for _ in 0..backoff {
core::hint::spin_loop();
}
if backoff < 64 {
backoff = backoff.saturating_mul(2);
} else {
#[cfg(feature = "std")]
{
std::thread::yield_now();
}
backoff = 1;
}
}
}
}
}
#[inline]
pub fn try_dequeue(&self) -> Option<T> {
let (pos, item) = self.claim_front()?;
unsafe { self.reopen(pos) };
Some(item)
}
#[inline]
pub(super) fn claim_front(&self) -> Option<(usize, T)> {
let mut pos = self.head.load(Ordering::Relaxed);
loop {
let slot = &self.buffer[pos & self.mask];
let seq = slot.sequence.load(Ordering::Acquire);
#[allow(clippy::cast_possible_wrap)]
let diff = seq.wrapping_sub(pos.wrapping_add(1)) as isize;
match diff.cmp(&0) {
CmpOrdering::Equal => {
match self.head.compare_exchange_weak(
pos,
pos.wrapping_add(1),
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => {
let item = unsafe { (*slot.data.get()).assume_init_read() };
return Some((pos, item));
}
Err(actual) => pos = actual,
}
}
CmpOrdering::Less => return None,
CmpOrdering::Greater => pos = self.head.load(Ordering::Relaxed),
}
}
}
#[inline]
pub(super) unsafe fn reopen(&self, pos: usize) {
self.buffer[pos & self.mask]
.sequence
.store(pos.wrapping_add(self.ring_len), Ordering::Release);
}
pub fn is_empty(&self) -> bool {
self.tail.load(Ordering::Acquire) == self.head.load(Ordering::Acquire)
}
pub fn is_full(&self) -> bool {
self.tail
.load(Ordering::Acquire)
.wrapping_sub(self.head.load(Ordering::Acquire))
>= self.capacity
}
pub const fn capacity(&self) -> usize {
self.capacity
}
pub fn len(&self) -> usize {
self.tail
.load(Ordering::Acquire)
.wrapping_sub(self.head.load(Ordering::Acquire))
}
}
#[inline]
fn wait_for_reopen(waits: &mut u32) {
#[cfg(test)]
REOPEN_WAITS.fetch_add(1, Ordering::Relaxed);
if *waits < SPINS_BEFORE_YIELD {
*waits += 1;
core::hint::spin_loop();
} else {
#[cfg(feature = "std")]
std::thread::yield_now();
#[cfg(not(feature = "std"))]
core::hint::spin_loop();
}
}
impl<T> Default for LockFreeQueue<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Drop for LockFreeQueue<T> {
fn drop(&mut self) {
let head = *self.head.0.get_mut();
let tail = *self.tail.0.get_mut();
for pos in head..tail {
let slot = &mut self.buffer[pos & self.mask];
if *slot.sequence.get_mut() == pos.wrapping_add(1) {
unsafe {
(*slot.data.get()).assume_init_drop();
}
}
}
}
}