shuttle/sync/
once.rs

1use crate::runtime::execution::ExecutionState;
2use crate::runtime::storage::StorageKey;
3use crate::runtime::task::clock::VectorClock;
4use crate::sync::Mutex;
5use std::cell::RefCell;
6use std::rc::Rc;
7use tracing::trace;
8
9/// A synchronization primitive which can be used to run a one-time global initialization. Useful
10/// for one-time initialization for FFI or related functionality. This type can only be constructed
11/// with [`Once::new()`].
12#[derive(Debug)]
13pub struct Once {
14    // We use the address of the `Once` as an identifier, so it can't be zero-sized even though all
15    // its state is stored in ExecutionState storage
16    _dummy: usize,
17}
18
19/// A `Once` cell can either be `Running`, in which case a `Mutex` mediates racing threads trying to
20/// invoke `call_once`, or `Complete` once an initializer has completed, in which case the `Mutex`
21/// is no longer necessary.
22enum OnceInitState {
23    Running(Rc<Mutex<bool>>),
24    Complete(VectorClock),
25}
26
27impl std::fmt::Debug for OnceInitState {
28    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
29        match self {
30            Self::Running(_) => write!(f, "Running"),
31            Self::Complete(_) => write!(f, "Complete"),
32        }
33    }
34}
35
36impl Once {
37    /// Creates a new `Once` value.
38    #[must_use]
39    #[allow(clippy::new_without_default)]
40    pub const fn new() -> Self {
41        Self { _dummy: 0 }
42    }
43
44    /// Performs an initialization routine once and only once. The given closure will be executed
45    /// if this is the first time `call_once` has been called, and otherwise the routine will *not*
46    /// be invoked.
47    ///
48    /// This method will block the calling thread if another initialization routine is currently
49    /// running.
50    ///
51    /// When this function returns, it is guaranteed that some initialization has run and completed
52    /// (it may not be the closure specified).
53    pub fn call_once<F>(&self, f: F)
54    where
55        F: FnOnce(),
56    {
57        self.call_once_inner(|_state| f(), false);
58    }
59
60    /// Performs the same function as [`Once::call_once()`] except ignores poisoning.
61    ///
62    /// If the cell has previously been poisoned, this function will still attempt to call the given
63    /// closure. If the closure does not panic, the cell will no longer be poisoned.
64    pub fn call_once_force<F>(&self, f: F)
65    where
66        F: FnOnce(&OnceState),
67    {
68        self.call_once_inner(f, true);
69    }
70
71    /// Returns `true` if some [`Once::call_once()`] call has completed successfully.
72    pub fn is_completed(&self) -> bool {
73        ExecutionState::with(|state| {
74            let init = match self.get_state(state) {
75                Some(init) => init,
76                None => return false,
77            };
78            let init_state = init.borrow();
79            match &*init_state {
80                OnceInitState::Complete(clock) => {
81                    let clock = clock.clone();
82                    drop(init_state);
83                    state.update_clock(&clock);
84                    true
85                }
86                _ => false,
87            }
88        })
89    }
90
91    fn call_once_inner<F>(&self, f: F, ignore_poisoning: bool)
92    where
93        F: FnOnce(&OnceState),
94    {
95        let lock = ExecutionState::with(|state| {
96            // Initialize the state of the `Once` cell if we're the first thread to try
97            if self.get_state(state).is_none() {
98                self.init_state(state, OnceInitState::Running(Rc::new(Mutex::new(false))));
99            }
100
101            let init = self.get_state(state).expect("must be initialized by this point");
102            let init_state = init.borrow();
103            trace!(state=?init_state, "call_once on cell {:p}", self);
104            match &*init_state {
105                OnceInitState::Complete(clock) => {
106                    // If already complete, just update the clock from the thread that inited
107                    let clock = clock.clone();
108                    drop(init_state);
109                    state.update_clock(&clock);
110                    None
111                }
112                OnceInitState::Running(lock) => Some(Rc::clone(lock)),
113            }
114        });
115
116        // If there's a lock, then we need to try racing on it to decide who gets to run their
117        // initialization closure.
118        if let Some(lock) = lock {
119            let (mut flag, is_poisoned) = match lock.lock() {
120                Ok(flag) => (flag, false),
121                Err(_) if !ignore_poisoning => panic!("Once instance has previously been poisoned"),
122                Err(err) => (err.into_inner(), true),
123            };
124            if *flag {
125                return;
126            }
127
128            trace!("won the call_once race for cell {:p}", self);
129            f(&OnceState(is_poisoned));
130
131            *flag = true;
132            // We were the thread that won the race, so remember our current clock to establish
133            // causality with future threads that try (and fail) to run `call_once`. The threads
134            // that were racing with us will get causality through acquiring the `Mutex`.
135            ExecutionState::with(|state| {
136                let clock = state.increment_clock().clone();
137                *self
138                    .get_state(state)
139                    .expect("must be initialized by this point")
140                    .borrow_mut() = OnceInitState::Complete(clock);
141            });
142        }
143    }
144
145    fn get_state<'a>(&self, from: &'a ExecutionState) -> Option<&'a RefCell<OnceInitState>> {
146        from.get_storage::<_, RefCell<OnceInitState>>(self)
147    }
148
149    fn init_state(&self, into: &mut ExecutionState, new_state: OnceInitState) {
150        into.init_storage::<_, RefCell<OnceInitState>>(self, RefCell::new(new_state));
151    }
152}
153
154/// State yielded to [`Once::call_once_force()`]'s closure parameter. The state can be used to query
155/// the poison status of the [`Once`].
156#[derive(Debug)]
157#[non_exhaustive]
158pub struct OnceState(bool);
159
160impl OnceState {
161    /// Returns `true` if the associated [`Once`] was poisoned prior to the invocation of the
162    /// closure passed to [`Once::call_once_force()`].
163    pub fn is_poisoned(&self) -> bool {
164        self.0
165    }
166}
167
168impl From<&Once> for StorageKey {
169    fn from(once: &Once) -> Self {
170        StorageKey(once as *const _ as usize, 0x2)
171    }
172}