#![allow(unsafe_code)]
use core::ptr::NonNull;
use core::task::{Poll, Waker};
use super::node::{FUTURE_REF, Seen, Waiter, release};
#[cfg(all(loom, test))]
use super::spin_probe;
use crate::sync::queue::WaitQueue;
use crate::sync::spin::pause;
pub(in crate::sync) enum Cancel {
Idle,
Withdrawn(bool, Shelved),
Granted(usize),
}
#[must_use = "bind it, so it drops after the withdrawal's pass"]
pub(in crate::sync) struct Shelved(
#[expect(dead_code, reason = "held to be dropped")] Option<Waker>,
);
pub(in crate::sync) enum Park {
Granted(usize),
Nudged,
Pending,
}
pub(in crate::sync) struct Wait {
node: Option<NonNull<Waiter>>,
}
impl Wait {
pub(in crate::sync) const fn new() -> Self {
Self { node: None }
}
#[inline]
pub(in crate::sync) fn is_queued(&self) -> bool {
self.node.is_some()
}
pub(in crate::sync) fn enqueue(&mut self, queue: &WaitQueue, payload: usize, waker: &Waker) {
debug_assert!(self.node.is_none(), "a future queues twice");
let waker = waker.clone();
let node = queue.pool().take();
unsafe { node.as_ref() }.arm(payload, waker);
queue.push(node);
self.node = Some(node);
}
pub(in crate::sync) fn park(&mut self, queue: &WaitQueue, waker: Option<&Waker>) -> Park {
let Some(node) = self.node else {
return Park::Granted(0);
};
let waiter = unsafe { node.as_ref() };
match waiter.park(waker) {
Seen::Granted => {
let payload = waiter.payload();
self.node = None;
release(node, FUTURE_REF, queue);
Park::Granted(payload)
}
Seen::Nudged => Park::Nudged,
Seen::Pending => Park::Pending,
}
}
pub(in crate::sync) fn spin_park(&mut self, queue: &WaitQueue, waker: &Waker) -> Park {
if let Some(node) = self.node {
let rounds = queue.spin.rounds();
let waiter = unsafe { node.as_ref() };
if rounds > 0 && waiter.begin_spin() {
#[cfg(all(loom, test))]
spin_probe::count(&spin_probe::SPINS);
if (0..rounds).any(|_| {
pause();
waiter.marked()
}) {
#[cfg(all(loom, test))]
spin_probe::count(&spin_probe::MARKED_WHILE_SPINNING);
queue.spin.won(rounds);
} else {
queue.spin.ran_out(rounds);
#[cfg(all(loom, test))]
{
if spin_probe::SKIP_LOOK.with(core::cell::Cell::get) {
return waiter.arm_without_look(waker);
}
let park = self.park(queue, Some(waker));
if !matches!(park, Park::Pending) {
spin_probe::count(&spin_probe::MARKED_AFTER_LAST_READ);
}
return park;
}
}
}
}
self.park(queue, Some(waker))
}
pub(in crate::sync) fn granted(&mut self, queue: &WaitQueue) -> Option<usize> {
let node = self.node?;
let waiter = unsafe { node.as_ref() };
if !waiter.is_granted() {
return None;
}
let payload = waiter.payload();
self.node = None;
release(node, FUTURE_REF, queue);
Some(payload)
}
pub(in crate::sync) fn poll(
&mut self,
queue: &WaitQueue,
waker: Option<&Waker>,
) -> Poll<usize> {
match self.park(queue, waker) {
Park::Granted(payload) => Poll::Ready(payload),
Park::Nudged | Park::Pending => Poll::Pending,
}
}
pub(in crate::sync) fn owe(&self) -> bool {
let Some(node) = self.node else {
return false;
};
unsafe { node.as_ref() }.owe()
}
pub(in crate::sync) fn unowe(&self) -> bool {
let Some(node) = self.node else {
return false;
};
unsafe { node.as_ref() }.unowe()
}
pub(in crate::sync) fn cancel(&mut self, queue: &WaitQueue) -> Cancel {
let Some(node) = self.node.take() else {
return Cancel::Idle;
};
let waiter = unsafe { node.as_ref() };
let outcome = match waiter.withdraw() {
Some((waker, nudged)) => Cancel::Withdrawn(nudged, Shelved(waker)),
None => Cancel::Granted(waiter.payload()),
};
release(node, FUTURE_REF, queue);
outcome
}
}
unsafe impl Send for Wait {}
unsafe impl Sync for Wait {}
#[cfg(all(test, not(loom)))]
mod tests {
use super::super::wake::WakeList;
use super::*;
use crate::sync::test_support::Polled;
fn armed() -> (WaitQueue, Wait, Polled<core::future::Pending<()>>) {
let queue = WaitQueue::new(0);
let counter = Polled::new(core::future::pending::<()>());
let mut wait = Wait::new();
wait.enqueue(&queue, 1, &counter.waker());
assert!(matches!(wait.park(&queue, None), Park::Pending));
(queue, wait, counter)
}
fn node(wait: &Wait) -> NonNull<Waiter> {
wait.node.expect("queued")
}
#[test]
fn a_nudge_that_finds_its_waiter_spinning_marks_it_and_wakes_nobody() {
let (queue, mut wait, counter) = armed();
let waiter = unsafe { node(&wait).as_ref() };
assert!(waiter.begin_spin(), "nothing marked yet: the waiter spins");
let mut woken = WakeList::new(&queue);
assert!(woken.nudge(node(&wait)), "a nudge is on its way");
drop(woken);
assert_eq!(
counter.wakes(),
0,
"a spinning waiter's waker is not called"
);
assert!(waiter.marked(), "the spin sees the mark");
assert!(matches!(
wait.park(&queue, Some(&counter.waker())),
Park::Nudged
));
assert!(matches!(wait.cancel(&queue), Cancel::Withdrawn(false, _)));
}
#[test]
fn a_nudge_that_finds_its_waiter_armed_calls_its_waker() {
let (queue, mut wait, counter) = armed();
let mut woken = WakeList::new(&queue);
assert!(woken.nudge(node(&wait)));
drop(woken);
assert_eq!(counter.wakes(), 1, "an armed waiter is woken");
assert!(matches!(
wait.park(&queue, Some(&counter.waker())),
Park::Nudged
));
assert!(matches!(wait.cancel(&queue), Cancel::Withdrawn(false, _)));
}
#[test]
fn a_nudge_as_the_spin_gives_up_is_seen_by_the_look() {
let (queue, mut wait, counter) = armed();
let waiter = unsafe { node(&wait).as_ref() };
assert!(waiter.begin_spin());
assert!(!waiter.marked(), "the spin runs out with nothing marked");
let mut woken = WakeList::new(&queue);
assert!(woken.nudge(node(&wait)), "the nudge lands as it gives up");
drop(woken);
assert_eq!(counter.wakes(), 0);
assert!(
matches!(wait.park(&queue, Some(&counter.waker())), Park::Nudged),
"the look found the nudge; the waiter does not sleep beside it"
);
assert!(matches!(wait.cancel(&queue), Cancel::Withdrawn(false, _)));
}
#[test]
fn a_marked_node_does_not_spin() {
let (queue, mut wait, counter) = armed();
let mut woken = WakeList::new(&queue);
assert!(woken.nudge(node(&wait)));
drop(woken);
assert!(!unsafe { node(&wait).as_ref() }.begin_spin());
assert!(matches!(
wait.park(&queue, Some(&counter.waker())),
Park::Nudged
));
assert!(matches!(wait.cancel(&queue), Cancel::Withdrawn(false, _)));
}
}