use crate::runtime::execution::ExecutionState;
use crate::runtime::storage::StorageKey;
use crate::runtime::task::clock::VectorClock;
use crate::sync::Mutex;
use std::cell::RefCell;
use std::rc::Rc;
use tracing::trace;
#[derive(Debug)]
pub struct Once {
_dummy: usize,
}
enum OnceInitState {
Running(Rc<Mutex<bool>>),
Complete(VectorClock),
}
impl std::fmt::Debug for OnceInitState {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Running(_) => write!(f, "Running"),
Self::Complete(_) => write!(f, "Complete"),
}
}
}
impl Once {
#[must_use]
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self { _dummy: 0 }
}
pub fn call_once<F>(&self, f: F)
where
F: FnOnce(),
{
self.call_once_inner(|_state| f(), false);
}
pub fn call_once_force<F>(&self, f: F)
where
F: FnOnce(&OnceState),
{
self.call_once_inner(f, true);
}
pub fn is_completed(&self) -> bool {
ExecutionState::with(|state| {
let init = match self.get_state(state) {
Some(init) => init,
None => return false,
};
let init_state = init.borrow();
match &*init_state {
OnceInitState::Complete(clock) => {
let clock = clock.clone();
drop(init_state);
state.update_clock(&clock);
true
}
_ => false,
}
})
}
fn call_once_inner<F>(&self, f: F, ignore_poisoning: bool)
where
F: FnOnce(&OnceState),
{
let lock = ExecutionState::with(|state| {
if self.get_state(state).is_none() {
self.init_state(state, OnceInitState::Running(Rc::new(Mutex::new(false))));
}
let init = self.get_state(state).expect("must be initialized by this point");
let init_state = init.borrow();
trace!(state=?init_state, "call_once on cell {:p}", self);
match &*init_state {
OnceInitState::Complete(clock) => {
let clock = clock.clone();
drop(init_state);
state.update_clock(&clock);
None
}
OnceInitState::Running(lock) => Some(Rc::clone(lock)),
}
});
if let Some(lock) = lock {
let (mut flag, is_poisoned) = match lock.lock() {
Ok(flag) => (flag, false),
Err(_) if !ignore_poisoning => panic!("Once instance has previously been poisoned"),
Err(err) => (err.into_inner(), true),
};
if *flag {
return;
}
trace!("won the call_once race for cell {:p}", self);
f(&OnceState(is_poisoned));
*flag = true;
ExecutionState::with(|state| {
let clock = state.increment_clock().clone();
*self
.get_state(state)
.expect("must be initialized by this point")
.borrow_mut() = OnceInitState::Complete(clock);
});
}
}
fn get_state<'a>(&self, from: &'a ExecutionState) -> Option<&'a RefCell<OnceInitState>> {
from.get_storage::<_, RefCell<OnceInitState>>(self)
}
fn init_state(&self, into: &mut ExecutionState, new_state: OnceInitState) {
into.init_storage::<_, RefCell<OnceInitState>>(self, RefCell::new(new_state));
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct OnceState(bool);
impl OnceState {
pub fn is_poisoned(&self) -> bool {
self.0
}
}
impl From<&Once> for StorageKey {
fn from(once: &Once) -> Self {
StorageKey(once as *const _ as usize, 0x2)
}
}