dispatch2/once.rs
1use core::cell::UnsafeCell;
2use core::ffi::c_void;
3use core::fmt;
4use core::panic::{RefUnwindSafe, UnwindSafe};
5use core::ptr::NonNull;
6use core::sync::atomic::{AtomicIsize, Ordering};
7
8use crate::generated::dispatch_once_t;
9
10/// A low-level synchronization primitive for one-time global execution.
11///
12/// This is equivalent to [`std::sync::Once`], except that this uses the
13/// underlying system primitives from `libdispatch`, which:
14/// - Might result in less code-size overhead.
15/// - Aborts on panics in the initialization closure.
16///
17/// Generally, prefer [`std::sync::Once`] unless you have a specific need for
18/// this.
19///
20///
21/// # Example
22///
23/// Run a closure once for the duration of the entire program, without using
24/// [`std::sync::Once`].
25///
26/// ```
27/// use dispatch2::DispatchOnce;
28///
29/// static INIT: DispatchOnce = DispatchOnce::new();
30///
31/// INIT.call_once(|| {
32/// // run initialization here
33/// });
34/// ```
35///
36#[cfg_attr(not(feature = "std"), doc = "[`std::sync::Once`]: #std-not-enabled")]
37#[doc(alias = "dispatch_once_t")]
38pub struct DispatchOnce {
39 predicate: UnsafeCell<dispatch_once_t>,
40}
41
42// This is intentionally `extern "C"`, since libdispatch will not propagate an
43// internal panic, but will simply abort.
44extern "C" fn invoke_closure<F>(context: *mut c_void)
45where
46 F: FnOnce(),
47{
48 let context: *mut Option<F> = context.cast();
49 // SAFETY: Context was created below in `invoke_dispatch_once` from
50 // `&mut Option<F>`.
51 let closure: &mut Option<F> = unsafe { &mut *context };
52
53 // SAFETY: libdispatch is implemented correctly, and will only call this
54 // once (and we set it to be available before calling dispatch_once).
55 let closure = unsafe { closure.take().unwrap_unchecked() };
56
57 (closure)();
58}
59
60#[cfg_attr(
61 // DISPATCH_ONCE_INLINE_FASTPATH, see DispatchOnce::call_once below.
62 any(target_arch = "x86", target_arch = "x86_64", target_vendor = "apple"),
63 cold,
64 inline(never)
65)]
66fn invoke_dispatch_once<F>(predicate: NonNull<dispatch_once_t>, closure: F)
67where
68 F: FnOnce(),
69{
70 // Convert closure data to context parameter.
71 let mut closure = Some(closure);
72 let context: *mut Option<F> = &mut closure;
73 let context: *mut c_void = context.cast();
74
75 // SAFETY: The function and context are valid, and the predicate pointer
76 // is valid.
77 //
78 // NOTE: The documentation says:
79 // > The predicate must point to a variable stored in global or static
80 // > scope. The result of using a predicate with automatic or dynamic
81 // > storage (including Objective-C instance variables) is undefined.
82 //
83 // In Rust though, we have stronger guarantees, and can guarantee that the
84 // predicate is never moved while in use, because the `DispatchOnce`
85 // itself is not cloneable.
86 //
87 // Even if libdispatch may sometimes use the pointer as a condition
88 // variable, or may internally store a self-referential pointer, it can
89 // only do that while the DispatchOnce is in use somewhere (i.e. it should
90 // not be able to do that while the DispatchOnce is being moved).
91 //
92 // Outside of being moved, the DispatchOnce can only be in two states:
93 // - Initialized.
94 // - Done.
95 //
96 // And those two states are freely movable.
97 unsafe { DispatchOnce::once_f(predicate, context, invoke_closure::<F>) };
98
99 // Closure is dropped here, depending on if it was executed (and taken
100 // from the `Option`) by `dispatch_once_f` or not.
101}
102
103impl DispatchOnce {
104 /// Creates a new `DispatchOnce`.
105 #[inline]
106 #[allow(clippy::new_without_default)] // `std::sync::Once` doesn't have it either
107 pub const fn new() -> Self {
108 Self {
109 predicate: UnsafeCell::new(0),
110 }
111 }
112
113 /// Executes a closure once for the lifetime of the application.
114 ///
115 /// If called simultaneously from multiple threads, this function waits
116 /// synchronously until the work function has completed.
117 ///
118 ///
119 /// # Aborts
120 ///
121 /// The process will trap or abort if:
122 /// - The given initialization closure unwinds.
123 /// - The given closure recursively invokes `call_once` on the same
124 /// `DispatchOnce` instance.
125 #[inline]
126 #[doc(alias = "dispatch_once")]
127 #[doc(alias = "dispatch_once_f")]
128 pub fn call_once<F>(&self, work: F)
129 where
130 F: FnOnce(),
131 {
132 // Unwrap is fine, the pointer is valid so can never be NULL.
133 let predicate = NonNull::new(self.predicate.get()).unwrap();
134
135 // DISPATCH_ONCE_INLINE_FASTPATH
136 if cfg!(any(
137 target_arch = "x86",
138 target_arch = "x86_64",
139 target_vendor = "apple"
140 )) {
141 // On certain platforms, the ABI of the predicate is stable enough
142 // that we are allowed to read it to check if the condition is
143 // done yet.
144 //
145 // The code in C is inside `_dispatch_once_f` in dispatch/once.h:
146 //
147 // if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
148 // dispatch_once_f(predicate, context, function);
149 // } else {
150 // dispatch_compiler_barrier();
151 // }
152 // DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
153
154 // NOTE: To uphold the rules set by the Rust AM, we use an atomic
155 // comparison here to avoid a possible tear, even though the
156 // equivalent C code just loads the predicate un-atomically.
157 //
158 // SAFETY: The predicate is a valid atomic pointer.
159 // TODO: Use `AtomicIsize::from_ptr` once in MSRV.
160 let atomic_predicate: &AtomicIsize = unsafe { predicate.cast().as_ref() };
161
162 // We use an acquire load, as that's what's done internally in
163 // libdispatch, and matches what's done in Rust's std too:
164 // <https://github.com/swiftlang/swift-corelibs-libdispatch/blob/swift-6.0.3-RELEASE/src/once.c#L57>
165 // <https://github.com/rust-lang/rust/blob/1.83.0/library/std/src/sys/sync/once/queue.rs#L130>
166 if atomic_predicate.load(Ordering::Acquire) != !0 {
167 invoke_dispatch_once(predicate, work);
168 }
169
170 // NOTE: Unlike in C, we cannot use `core::hint::assert_unchecked`,
171 // since that would actually be lying from a language perspective;
172 // the value seems to only settle on being !0 after some time
173 // (something about the _COMM_PAGE_CPU_QUIESCENT_COUNTER?)
174 //
175 // TODO: Investigate this further!
176 // core::hint::assert_unchecked(atomic_predicate.load(Ordering::Acquire) == !0);
177 } else {
178 invoke_dispatch_once(predicate, work);
179 }
180 }
181}
182
183// SAFETY: Same as `std::sync::Once`
184unsafe impl Send for DispatchOnce {}
185
186// SAFETY: Same as `std::sync::Once`
187unsafe impl Sync for DispatchOnce {}
188
189impl UnwindSafe for DispatchOnce {}
190impl RefUnwindSafe for DispatchOnce {}
191
192impl fmt::Debug for DispatchOnce {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 f.debug_struct("DispatchOnce").finish_non_exhaustive()
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use core::cell::Cell;
201 use core::mem::ManuallyDrop;
202
203 use super::*;
204
205 #[test]
206 fn test_static() {
207 static ONCE: DispatchOnce = DispatchOnce::new();
208 let mut num = 0;
209 ONCE.call_once(|| num += 1);
210 ONCE.call_once(|| num += 1);
211 assert!(num == 1);
212 }
213
214 #[test]
215 fn test_in_loop() {
216 let once = DispatchOnce::new();
217
218 let mut call_count = 0;
219 for _ in 0..10 {
220 once.call_once(|| call_count += 1);
221 }
222
223 assert_eq!(call_count, 1);
224 }
225
226 #[test]
227 fn test_move() {
228 let once = DispatchOnce::new();
229
230 let mut call_count = 0;
231 for _ in 0..10 {
232 once.call_once(|| call_count += 1);
233 }
234
235 #[allow(clippy::redundant_locals)]
236 let once = once;
237 for _ in 0..10 {
238 once.call_once(|| call_count += 1);
239 }
240
241 let once = DispatchOnce {
242 predicate: UnsafeCell::new(once.predicate.into_inner()),
243 };
244 for _ in 0..10 {
245 once.call_once(|| call_count += 1);
246 }
247
248 assert_eq!(call_count, 1);
249 }
250
251 #[test]
252 #[cfg(feature = "std")]
253 fn test_threaded() {
254 let once = DispatchOnce::new();
255
256 let num = AtomicIsize::new(0);
257
258 std::thread::scope(|scope| {
259 scope.spawn(|| {
260 once.call_once(|| {
261 num.fetch_add(1, Ordering::Relaxed);
262 });
263 });
264 scope.spawn(|| {
265 once.call_once(|| {
266 num.fetch_add(1, Ordering::Relaxed);
267 });
268 });
269 scope.spawn(|| {
270 once.call_once(|| {
271 num.fetch_add(1, Ordering::Relaxed);
272 });
273 });
274 });
275
276 assert!(num.load(Ordering::Relaxed) == 1);
277 }
278
279 #[derive(Clone)]
280 struct DropTest<'a>(&'a Cell<usize>);
281
282 impl Drop for DropTest<'_> {
283 fn drop(&mut self) {
284 self.0.set(self.0.get() + 1);
285 }
286 }
287
288 #[test]
289 fn test_drop_in_closure() {
290 let amount_of_drops = Cell::new(0);
291 let once = DispatchOnce::new();
292
293 let tester = DropTest(&amount_of_drops);
294 once.call_once(move || {
295 let _tester = tester;
296 });
297 assert_eq!(amount_of_drops.get(), 1);
298
299 let tester = DropTest(&amount_of_drops);
300 once.call_once(move || {
301 let _tester = tester;
302 });
303 assert_eq!(amount_of_drops.get(), 2);
304 }
305
306 #[test]
307 fn test_drop_in_closure_with_leak() {
308 let amount_of_drops = Cell::new(0);
309 let once = DispatchOnce::new();
310
311 // Not dropped here, since we ManuallyDrop inside the closure (and the
312 // closure is executed).
313 let tester = DropTest(&amount_of_drops);
314 once.call_once(move || {
315 let _tester = ManuallyDrop::new(tester);
316 });
317 assert_eq!(amount_of_drops.get(), 0);
318
319 // Still dropped here, since the once is not executed
320 let tester = DropTest(&amount_of_drops);
321 once.call_once(move || {
322 let _tester = ManuallyDrop::new(tester);
323 });
324 assert_eq!(amount_of_drops.get(), 1);
325 }
326
327 #[test]
328 #[ignore = "traps the process (as expected)"]
329 fn test_recursive_invocation() {
330 let once = DispatchOnce::new();
331
332 once.call_once(|| {
333 once.call_once(|| {});
334 });
335 }
336}