Skip to main content

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}