use std::{
cell::UnsafeCell,
marker::PhantomData,
panic::{RefUnwindSafe, UnwindSafe},
ptr,
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
thread::{self, Thread},
};
#[derive(Debug)]
pub(crate) struct OnceCell<T> {
state: AtomicUsize,
_marker: PhantomData<*mut Waiter>,
pub(crate) value: UnsafeCell<Option<T>>,
}
unsafe impl<T: Sync + Send> Sync for OnceCell<T> {}
unsafe impl<T: Send> Send for OnceCell<T> {}
impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for OnceCell<T> {}
impl<T: UnwindSafe> UnwindSafe for OnceCell<T> {}
const INCOMPLETE: usize = 0x0;
const RUNNING: usize = 0x1;
const COMPLETE: usize = 0x2;
const STATE_MASK: usize = 0x3;
struct Waiter {
thread: Option<Thread>,
signaled: AtomicBool,
next: *mut Waiter,
}
struct Finish<'a> {
failed: bool,
my_state: &'a AtomicUsize,
}
impl<T> OnceCell<T> {
pub(crate) const fn new() -> OnceCell<T> {
OnceCell {
state: AtomicUsize::new(INCOMPLETE),
_marker: PhantomData,
value: UnsafeCell::new(None),
}
}
#[inline]
pub(crate) fn is_initialized(&self) -> bool {
self.state.load(Ordering::Acquire) == COMPLETE
}
#[cold]
pub(crate) fn initialize<F, E>(&self, f: F) -> Result<(), E>
where
F: FnOnce() -> Result<T, E>,
{
let mut f = Some(f);
let mut res: Result<(), E> = Ok(());
let slot = &self.value;
initialize_inner(&self.state, &mut || {
let f = f.take().unwrap();
match f() {
Ok(value) => {
unsafe { *slot.get() = Some(value) };
true
}
Err(e) => {
res = Err(e);
false
}
}
});
res
}
}
fn initialize_inner(my_state: &AtomicUsize, init: &mut dyn FnMut() -> bool) -> bool {
let mut state = my_state.load(Ordering::SeqCst);
'outer: loop {
match state {
COMPLETE => return true,
INCOMPLETE => {
let old = my_state.compare_and_swap(state, RUNNING, Ordering::SeqCst);
if old != state {
state = old;
continue;
}
let mut complete = Finish { failed: true, my_state };
let success = init();
complete.failed = !success;
return success;
}
_ => {
assert!(state & STATE_MASK == RUNNING);
let mut node = Waiter {
thread: Some(thread::current()),
signaled: AtomicBool::new(false),
next: ptr::null_mut(),
};
let me = &mut node as *mut Waiter as usize;
assert!(me & STATE_MASK == 0);
while state & STATE_MASK == RUNNING {
node.next = (state & !STATE_MASK) as *mut Waiter;
let old = my_state.compare_and_swap(state, me | RUNNING, Ordering::SeqCst);
if old != state {
state = old;
continue;
}
while !node.signaled.load(Ordering::SeqCst) {
thread::park();
}
state = my_state.load(Ordering::SeqCst);
continue 'outer;
}
}
}
}
}
impl Drop for Finish<'_> {
fn drop(&mut self) {
let queue = if self.failed {
self.my_state.swap(INCOMPLETE, Ordering::SeqCst)
} else {
self.my_state.swap(COMPLETE, Ordering::SeqCst)
};
assert_eq!(queue & STATE_MASK, RUNNING);
unsafe {
let mut queue = (queue & !STATE_MASK) as *mut Waiter;
while !queue.is_null() {
let next = (*queue).next;
let thread = (*queue).thread.take().unwrap();
(*queue).signaled.store(true, Ordering::SeqCst);
thread.unpark();
queue = next;
}
}
}
}
#[cfg(test)]
mod tests {
use std::panic;
#[cfg(not(miri))] use std::{sync::mpsc::channel, thread};
use super::OnceCell;
impl<T> OnceCell<T> {
fn init(&self, f: impl FnOnce() -> T) {
enum Void {}
let _ = self.initialize(|| Ok::<T, Void>(f()));
}
}
#[test]
fn smoke_once() {
static O: OnceCell<()> = OnceCell::new();
let mut a = 0;
O.init(|| a += 1);
assert_eq!(a, 1);
O.init(|| a += 1);
assert_eq!(a, 1);
}
#[test]
#[cfg(not(miri))] fn stampede_once() {
static O: OnceCell<()> = OnceCell::new();
static mut RUN: bool = false;
let (tx, rx) = channel();
for _ in 0..10 {
let tx = tx.clone();
thread::spawn(move || {
for _ in 0..4 {
thread::yield_now()
}
unsafe {
O.init(|| {
assert!(!RUN);
RUN = true;
});
assert!(RUN);
}
tx.send(()).unwrap();
});
}
unsafe {
O.init(|| {
assert!(!RUN);
RUN = true;
});
assert!(RUN);
}
for _ in 0..10 {
rx.recv().unwrap();
}
}
#[test]
#[cfg(not(miri))] fn poison_bad() {
static O: OnceCell<()> = OnceCell::new();
let t = panic::catch_unwind(|| {
O.init(|| panic!());
});
assert!(t.is_err());
let mut called = false;
O.init(|| {
called = true;
});
assert!(called);
O.init(|| {});
}
#[test]
#[cfg(not(miri))] fn wait_for_force_to_finish() {
static O: OnceCell<()> = OnceCell::new();
let t = panic::catch_unwind(|| {
O.init(|| panic!());
});
assert!(t.is_err());
let (tx1, rx1) = channel();
let (tx2, rx2) = channel();
let t1 = thread::spawn(move || {
O.init(|| {
tx1.send(()).unwrap();
rx2.recv().unwrap();
});
});
rx1.recv().unwrap();
let t2 = thread::spawn(|| {
let mut called = false;
O.init(|| {
called = true;
});
assert!(!called);
});
tx2.send(()).unwrap();
assert!(t1.join().is_ok());
assert!(t2.join().is_ok());
}
}