use crate::cell::Cell;
use crate::fmt;
use crate::ptr;
use crate::sync as public;
use crate::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use crate::sync::once::ExclusiveState;
use crate::thread::{self, Thread};
type Masked = ();
pub struct Once {
state_and_queue: AtomicPtr<Masked>,
}
pub struct OnceState {
poisoned: bool,
set_state_on_drop_to: Cell<*mut Masked>,
}
const INCOMPLETE: usize = 0x0;
const POISONED: usize = 0x1;
const RUNNING: usize = 0x2;
const COMPLETE: usize = 0x3;
const STATE_MASK: usize = 0x3;
#[repr(align(4))] struct Waiter {
thread: Cell<Option<Thread>>,
signaled: AtomicBool,
next: *const Waiter,
}
struct WaiterQueue<'a> {
state_and_queue: &'a AtomicPtr<Masked>,
set_state_on_drop_to: *mut Masked,
}
impl Once {
#[inline]
#[rustc_const_stable(feature = "const_once_new", since = "1.32.0")]
pub const fn new() -> Once {
Once { state_and_queue: AtomicPtr::new(ptr::invalid_mut(INCOMPLETE)) }
}
#[inline]
pub fn is_completed(&self) -> bool {
self.state_and_queue.load(Ordering::Acquire).addr() == COMPLETE
}
#[inline]
pub(crate) fn state(&mut self) -> ExclusiveState {
match self.state_and_queue.get_mut().addr() {
INCOMPLETE => ExclusiveState::Incomplete,
POISONED => ExclusiveState::Poisoned,
COMPLETE => ExclusiveState::Complete,
_ => unreachable!("invalid Once state"),
}
}
#[cold]
#[track_caller]
pub fn call(&self, ignore_poisoning: bool, init: &mut dyn FnMut(&public::OnceState)) {
let mut state_and_queue = self.state_and_queue.load(Ordering::Acquire);
loop {
match state_and_queue.addr() {
COMPLETE => break,
POISONED if !ignore_poisoning => {
panic!("Once instance has previously been poisoned");
}
POISONED | INCOMPLETE => {
let exchange_result = self.state_and_queue.compare_exchange(
state_and_queue,
ptr::invalid_mut(RUNNING),
Ordering::Acquire,
Ordering::Acquire,
);
if let Err(old) = exchange_result {
state_and_queue = old;
continue;
}
let mut waiter_queue = WaiterQueue {
state_and_queue: &self.state_and_queue,
set_state_on_drop_to: ptr::invalid_mut(POISONED),
};
let init_state = public::OnceState {
inner: OnceState {
poisoned: state_and_queue.addr() == POISONED,
set_state_on_drop_to: Cell::new(ptr::invalid_mut(COMPLETE)),
},
};
init(&init_state);
waiter_queue.set_state_on_drop_to = init_state.inner.set_state_on_drop_to.get();
break;
}
_ => {
assert!(state_and_queue.addr() & STATE_MASK == RUNNING);
wait(&self.state_and_queue, state_and_queue);
state_and_queue = self.state_and_queue.load(Ordering::Acquire);
}
}
}
}
}
fn wait(state_and_queue: &AtomicPtr<Masked>, mut current_state: *mut Masked) {
loop {
if current_state.addr() & STATE_MASK != RUNNING {
return;
}
let node = Waiter {
thread: Cell::new(Some(thread::current())),
signaled: AtomicBool::new(false),
next: current_state.with_addr(current_state.addr() & !STATE_MASK) as *const Waiter,
};
let me = &node as *const Waiter as *const Masked as *mut Masked;
let exchange_result = state_and_queue.compare_exchange(
current_state,
me.with_addr(me.addr() | RUNNING),
Ordering::Release,
Ordering::Relaxed,
);
if let Err(old) = exchange_result {
current_state = old;
continue;
}
while !node.signaled.load(Ordering::Acquire) {
thread::park();
}
break;
}
}
#[stable(feature = "std_debug", since = "1.16.0")]
impl fmt::Debug for Once {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Once").finish_non_exhaustive()
}
}
impl Drop for WaiterQueue<'_> {
fn drop(&mut self) {
let state_and_queue =
self.state_and_queue.swap(self.set_state_on_drop_to, Ordering::AcqRel);
assert_eq!(state_and_queue.addr() & STATE_MASK, RUNNING);
unsafe {
let mut queue =
state_and_queue.with_addr(state_and_queue.addr() & !STATE_MASK) as *const Waiter;
while !queue.is_null() {
let next = (*queue).next;
let thread = (*queue).thread.take().unwrap();
(*queue).signaled.store(true, Ordering::Release);
queue = next;
thread.unpark();
}
}
}
}
impl OnceState {
#[inline]
pub fn is_poisoned(&self) -> bool {
self.poisoned
}
#[inline]
pub fn poison(&self) {
self.set_state_on_drop_to.set(ptr::invalid_mut(POISONED));
}
}