#![allow(unsafe_code)]
use core::ptr::{self, NonNull};
use super::internal::{AtomicPtr, AtomicUsize, Ordering};
use super::spin::Budget;
use super::waiter::{Pool, Wait, Waiter, WakeList, release_queued};
const DRAINING: usize = 1;
const DIRTY: usize = 1 << 1;
pub(super) const QUEUED: usize = 1 << 2;
pub(super) const NUDGE_OUT: usize = 1 << 3;
pub(super) const HANDOFF: usize = 1 << 4;
pub(super) const WAKE_FRONT: usize = 1 << 5;
pub(super) const FIRST_BIT: u32 = 6;
#[inline]
pub(super) fn wants_drain(prev: usize) -> bool {
prev & QUEUED != 0 && (prev & NUDGE_OUT == 0 || prev & HANDOFF != 0)
}
#[inline]
pub(super) fn stops_spin(state: usize) -> bool {
state & HANDOFF != 0 || state & (QUEUED | DRAINING) == QUEUED
}
pub(super) struct Nudges<'p, 'a> {
woken: &'p mut WakeList<'a>,
queue: &'p WaitQueue,
raised: bool,
sent: bool,
}
impl<'p, 'a> Nudges<'p, 'a> {
pub(super) fn new(woken: &'p mut WakeList<'a>, queue: &'p WaitQueue) -> Self {
Self {
woken,
queue,
raised: false,
sent: false,
}
}
pub(super) fn nudge(&mut self, node: NonNull<Waiter>) {
if !self.raised {
self.queue.state.fetch_or(NUDGE_OUT, Ordering::AcqRel);
self.raised = true;
}
self.sent |= self.woken.nudge(node);
}
pub(super) fn finish(self) {
if self.raised && !self.sent {
self.queue.state.fetch_and(!NUDGE_OUT, Ordering::AcqRel);
}
}
}
pub(super) enum Step {
Keep,
Set(usize),
Drain(usize),
Wake(usize),
}
pub(super) struct WaitQueue {
pub(super) state: AtomicUsize,
arrivals: AtomicPtr<Waiter>,
cancels: AtomicUsize,
owed: AtomicUsize,
pool: Pool,
pub(super) spin: Budget,
}
impl WaitQueue {
loom_const_fn! {
pub(super) fn new(state: usize) -> Self {
Self::with_spin(state, Budget::new())
}
}
loom_const_fn! {
pub(super) fn without_spin(state: usize) -> Self {
Self::with_spin(state, Budget::never())
}
}
loom_const_fn! {
fn with_spin(state: usize, spin: Budget) -> Self {
Self {
state: AtomicUsize::new(state),
arrivals: AtomicPtr::new(ptr::null_mut()),
cancels: AtomicUsize::new(0),
owed: AtomicUsize::new(0),
pool: Pool::new(),
spin,
}
}
}
pub(super) fn pool(&self) -> &Pool {
&self.pool
}
pub(super) fn cancels(&self) -> usize {
self.cancels.load(Ordering::Relaxed)
}
pub(super) fn hands_off(&self) -> bool {
self.state.load(Ordering::Acquire) & HANDOFF != 0 && self.owed.load(Ordering::Acquire) != 0
}
pub(super) fn forgive(&self) {
self.owed.fetch_sub(1, Ordering::AcqRel);
}
pub(super) fn consume_nudge(&self) -> usize {
self.state.fetch_and(!NUDGE_OUT, Ordering::AcqRel)
}
pub(super) fn push(&self, node: NonNull<Waiter>) {
let mut head = self.arrivals.load(Ordering::Relaxed);
loop {
unsafe { node.as_ref() }.set_next(head);
match self.arrivals.compare_exchange_weak(
head,
node.as_ptr(),
Ordering::Release,
Ordering::Relaxed,
) {
Ok(_) => return,
Err(actual) => head = actual,
}
}
}
fn take(&self) -> Arrivals {
let mut cursor = self.arrivals.swap(ptr::null_mut(), Ordering::Acquire);
let mut fifo: *mut Waiter = ptr::null_mut();
while let Some(node) = NonNull::new(cursor) {
let waiter = unsafe { node.as_ref() };
cursor = waiter.next();
waiter.set_next(fifo);
fifo = node.as_ptr();
}
Arrivals(fifo)
}
}
impl Drop for WaitQueue {
fn drop(&mut self) {
for node in self.take() {
release_queued(node, self);
}
}
}
pub(super) struct Arrivals(*mut Waiter);
impl Iterator for Arrivals {
type Item = NonNull<Waiter>;
fn next(&mut self) -> Option<NonNull<Waiter>> {
let node = NonNull::new(self.0)?;
self.0 = unsafe { node.as_ref() }.next();
Some(node)
}
}
pub(super) trait Policy {
fn queue(&self) -> &WaitQueue;
fn pass(&self, arrivals: Arrivals, woken: &mut WakeList<'_>, wake_front: bool) -> usize;
fn transition(&self, mut f: impl FnMut(usize) -> Step) -> usize {
let state = &self.queue().state;
let mut current = state.load(Ordering::Acquire);
loop {
let (next, claim) = match f(current) {
Step::Keep => return current,
Step::Set(next) => (next, false),
Step::Drain(next) if current & DRAINING != 0 => (next | DIRTY, false),
Step::Wake(next) if current & DRAINING != 0 => (next, false),
Step::Drain(next) | Step::Wake(next) => (next | DRAINING, true),
};
match state.compare_exchange_weak(current, next, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) => {
if claim {
self.drain();
}
return current;
}
Err(actual) => current = actual,
}
}
}
fn drain(&self) {
let queue = self.queue();
let mut woken = WakeList::new(queue);
loop {
let before = queue
.state
.fetch_and(!(DIRTY | WAKE_FRONT), Ordering::AcqRel);
let mut clear = self.pass(queue.take(), &mut woken, before & WAKE_FRONT != 0);
if queue.owed.load(Ordering::Acquire) == 0 {
clear |= HANDOFF;
}
let (wanted, gone) = if clear & QUEUED != 0 {
(0, DRAINING | WAKE_FRONT | clear)
} else {
(WAKE_FRONT, DRAINING | clear)
};
let mut current = queue.state.load(Ordering::Acquire);
while current & (DIRTY | wanted) == 0 {
match queue.state.compare_exchange_weak(
current,
current & !gone,
Ordering::AcqRel,
Ordering::Acquire,
) {
Ok(_) => return,
Err(actual) => current = actual,
}
}
}
}
fn enqueue(&self, wait: &mut Wait, payload: usize, waker: &core::task::Waker) {
wait.enqueue(self.queue(), payload, waker);
self.transition(|state| Step::Drain(state | QUEUED));
}
fn withdrawn(&self, woken: bool) {
self.queue().cancels.fetch_add(1, Ordering::Relaxed);
let pass_on = if woken { WAKE_FRONT } else { 0 };
self.transition(|state| Step::Drain((state & !NUDGE_OUT) | pass_on));
}
fn released(&self) {
self.transition(|state| Step::Wake(state | WAKE_FRONT));
}
fn owe_or_take(
&self,
wait: &Wait,
mut take: impl FnMut(usize) -> Option<usize>,
) -> Result<(), usize> {
let queue = self.queue();
queue.owed.fetch_add(1, Ordering::AcqRel);
let marked = wait.owe();
let mut took = false;
let seen = if marked {
self.transition(|state| {
took = false;
if state & HANDOFF != 0 {
return Step::Keep;
}
match take(state) {
Some(next) => {
took = true;
Step::Set(next)
}
None => Step::Drain(state | HANDOFF),
}
})
} else {
queue.state.load(Ordering::Acquire)
};
if !marked || took || seen & HANDOFF != 0 {
if !marked || wait.unowe() {
queue.forgive();
}
if queue.state.fetch_or(0, Ordering::AcqRel) & HANDOFF != 0 {
self.transition(Step::Drain);
}
}
if took { Ok(()) } else { Err(seen) }
}
}