use std::{
cell::Cell,
sync::atomic::{AtomicBool, Ordering},
thread::{self, Thread},
};
use conquer_util::BackOff;
use crate::{
cell::{Block, Unblock},
state::{
AtomicOnceState, BlockedState,
OnceState::{Ready, Uninit, WouldBlock},
},
POISON_PANIC_MSG,
};
use self::internal::ParkThread;
#[cfg(any(test, feature = "std"))]
pub type Lazy<T, F = fn() -> T> = crate::lazy::Lazy<T, ParkThread, F>;
#[cfg(any(test, feature = "std"))]
pub type OnceCell<T> = crate::cell::OnceCell<T, ParkThread>;
#[cfg(any(test, feature = "std"))]
pub type Once = OnceCell<()>;
mod internal {
#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct ParkThread;
}
impl ParkThread {
#[inline]
pub(crate) fn try_block_spinning(
state: &AtomicOnceState,
back_off: &BackOff,
) -> Result<(), BlockedState> {
loop {
match state.load(Ordering::Acquire).expect(POISON_PANIC_MSG) {
Ready => return Ok(()),
WouldBlock(blocked) if back_off.advise_yield() => {
back_off.reset();
return Err(blocked);
}
_ => {}
}
back_off.spin();
}
}
}
impl Unblock for ParkThread {
#[inline]
unsafe fn on_unblock(state: BlockedState) {
let mut curr = state.as_ptr() as *const StackWaiter;
while !curr.is_null() {
let thread = {
#[allow(unused_unsafe)]
let waiter = unsafe { &*curr };
curr = waiter.next.get();
let thread = waiter.thread.take().unwrap();
waiter.ready.store(true, Ordering::Release);
thread
};
thread.unpark();
}
}
}
unsafe impl Block for ParkThread {
#[inline]
fn block(state: &AtomicOnceState) {
let back_off = BackOff::new();
let blocked = match Self::try_block_spinning(state, &back_off) {
Ok(_) => return,
Err(blocked) => blocked,
};
let waiter = StackWaiter {
ready: AtomicBool::new(false),
thread: Cell::new(Some(thread::current())),
next: Cell::new(blocked.as_ptr() as *const StackWaiter),
};
let mut curr = blocked;
let head = BlockedState::from(&waiter as *const _);
while let Err(err) = unsafe { state.try_enqueue_waiter(curr, head, Ordering::AcqRel) } {
match err {
WouldBlock(queue) => {
curr = queue;
waiter.next.set(queue.as_ptr() as *const StackWaiter);
back_off.spin();
}
Ready => return,
Uninit => unreachable!("cell state can not become `UNINIT again`"),
}
}
while !waiter.ready.load(Ordering::Acquire) {
thread::park();
}
assert_eq!(state.load(Ordering::Acquire).expect(POISON_PANIC_MSG), Ready);
}
}
#[repr(align(4))]
pub(crate) struct StackWaiter {
ready: AtomicBool,
thread: Cell<Option<Thread>>,
next: Cell<*const StackWaiter>,
}
#[cfg(test)]
mod tests {
generate_tests_non_blocking!();
generate_tests!();
}