use core::{
ptr,
sync::atomic::{AtomicPtr, AtomicUsize, Ordering, fence},
};
use crate::runtime::task_runtime;
const SLOT_COUNT: usize = 2;
const NO_RETIRING_GENERATION: usize = usize::MAX;
const PUBLISHER_OVERFLOW_INVARIANT: u32 = 0x494e_0002;
const PUBLISHER_UNDERFLOW_INVARIANT: u32 = 0x494e_0003;
const GENERATION_EXHAUSTED_INVARIANT: u32 = 0x494e_0004;
#[derive(Debug)]
pub(crate) struct EpochMpscQueue<Node> {
heads: [AtomicPtr<Node>; SLOT_COUNT],
active_generation: AtomicUsize,
slot_publishers: [AtomicUsize; SLOT_COUNT],
retiring_generation: AtomicUsize,
}
impl<Node> EpochMpscQueue<Node> {
pub(crate) const fn new() -> Self {
Self {
heads: [
AtomicPtr::new(ptr::null_mut()),
AtomicPtr::new(ptr::null_mut()),
],
active_generation: AtomicUsize::new(0),
slot_publishers: [AtomicUsize::new(0), AtomicUsize::new(0)],
retiring_generation: AtomicUsize::new(NO_RETIRING_GENERATION),
}
}
pub(crate) fn is_empty(&self) -> bool {
self.retiring_generation.load(Ordering::Acquire) == NO_RETIRING_GENERATION
&& self.heads[0].load(Ordering::Acquire).is_null()
&& self.heads[1].load(Ordering::Acquire).is_null()
}
pub(crate) unsafe fn publish(&self, node: *mut Node, next: &AtomicPtr<Node>) -> bool {
let publisher = GenerationPublisher::enter_stable(self);
let head = &self.heads[publisher.slot];
let mut observed = head.load(Ordering::Acquire);
loop {
next.store(observed, Ordering::Relaxed);
match head.compare_exchange_weak(observed, node, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) => return observed.is_null(),
Err(updated) => observed = updated,
}
}
}
pub(crate) unsafe fn take_graced_stack(&self) -> *mut Node {
let mut retiring = self.retiring_generation.load(Ordering::SeqCst);
if retiring == NO_RETIRING_GENERATION {
let active = self.active_generation.load(Ordering::SeqCst);
let active_slot = generation_slot(active);
if self.heads[active_slot].load(Ordering::Acquire).is_null() {
return ptr::null_mut();
}
let Some(next) = active.checked_add(1) else {
task_runtime::fatal_invariant(GENERATION_EXHAUSTED_INVARIANT, active);
};
if next == NO_RETIRING_GENERATION {
task_runtime::fatal_invariant(GENERATION_EXHAUSTED_INVARIANT, active);
}
self.active_generation.store(next, Ordering::SeqCst);
self.retiring_generation.store(active, Ordering::SeqCst);
fence(Ordering::SeqCst);
retiring = active;
}
let retiring_slot = generation_slot(retiring);
if self.slot_publishers[retiring_slot].load(Ordering::SeqCst) != 0 {
return ptr::null_mut();
}
let stack = self.heads[retiring_slot].swap(ptr::null_mut(), Ordering::AcqRel);
self.retiring_generation
.store(NO_RETIRING_GENERATION, Ordering::SeqCst);
stack
}
}
struct GenerationPublisher<'queue, Node> {
queue: &'queue EpochMpscQueue<Node>,
generation: usize,
slot: usize,
}
impl<'queue, Node> GenerationPublisher<'queue, Node> {
fn enter_stable(queue: &'queue EpochMpscQueue<Node>) -> Self {
loop {
let generation = queue.active_generation.load(Ordering::SeqCst);
let slot = generation_slot(generation);
increment_counter(
&queue.slot_publishers[slot],
PUBLISHER_OVERFLOW_INVARIANT,
generation,
);
fence(Ordering::SeqCst);
let publisher = Self {
queue,
generation,
slot,
};
if queue.active_generation.load(Ordering::SeqCst) == generation {
return publisher;
}
drop(publisher);
}
}
}
impl<Node> Drop for GenerationPublisher<'_, Node> {
fn drop(&mut self) {
let publishers = self.queue.slot_publishers[self.slot].fetch_sub(1, Ordering::SeqCst);
if publishers == 0 {
task_runtime::fatal_invariant(PUBLISHER_UNDERFLOW_INVARIANT, self.generation);
}
}
}
const fn generation_slot(generation: usize) -> usize {
generation & (SLOT_COUNT - 1)
}
fn increment_counter(counter: &AtomicUsize, invariant: u32, argument: usize) {
if counter.fetch_add(1, Ordering::SeqCst) == usize::MAX {
task_runtime::fatal_invariant(invariant, argument);
}
}