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}