Skip to main content

netstack3_sync/
once.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! One-time initialization primitives.
6
7use core::cell::UnsafeCell;
8use core::fmt;
9use core::mem::MaybeUninit;
10
11/// A synchronization primitive which can be used to run a one-time
12/// initialization.
13///
14/// # Safety
15///
16/// Implementations must guarantee that:
17/// 1. [`Once::once`] executes the provided closure at most once across all
18///    concurrent callers, and blocks all concurrent callers until that single
19///    execution completes, establishing an acquire/release synchronization edge
20///    from the completion of the closure to the return of [`Once::once`].
21/// 2. [`Once::is_completed`] returns `true` if and only if an invocation of
22///    [`Once::once`] has completed without panicking, establishing an acquire
23///    synchronization edge with the completion of that closure.
24/// 3. [`Default::default`] returns an uncompleted instance for which
25///    [`Once::is_completed`] returns `false`.
26pub unsafe trait Once: Default + Send + Sync + 'static {
27    /// Performs a one-time initialization routine.
28    ///
29    /// The given closure `f` will be executed if and only if this [`Once`]
30    /// has not already been completed. Concurrent calls to `once` block until
31    /// initialization finishes.
32    ///
33    /// # Panics
34    ///
35    /// If `f` panics, the implementation is expected to panic on further
36    /// attempts to initialize. Note that Netstack3 is usually used in
37    /// `panic=abort` compilations, so we don't need anything stronger than that.
38    fn once(&self, f: impl FnOnce());
39
40    /// Returns `true` if some [`Once::once`] call has completed successfully.
41    fn is_completed(&self) -> bool;
42}
43
44/// A synchronization primitive which can nominally be written to only once.
45pub struct OnceLock<T, O: Once> {
46    once: O,
47    data: UnsafeCell<MaybeUninit<T>>,
48}
49
50// SAFETY: `OnceLock<T, O>` owns a `T` (once initialized) and an `O`. Sending
51// it across threads requires `T: Send` and `O: Send`.
52// This is the same bound as std::sync::OnceLock.
53unsafe impl<T: Send, O: Once> Send for OnceLock<T, O> {}
54
55// SAFETY: Sharing `&OnceLock<T, O>` across threads allows concurrent calls to
56// `get_or_init` (which may initialize `T` on one thread and read `&T` on
57// another) and `get` (which returns `&T`). This requires `T: Send + Sync` and
58// `O: Sync`.
59// This is the same bound as std::sync::OnceLock.
60unsafe impl<T: Send + Sync, O: Once> Sync for OnceLock<T, O> {}
61
62impl<T, O: Once> Default for OnceLock<T, O> {
63    #[inline]
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69impl<T, O: Once> OnceLock<T, O> {
70    /// Creates a new empty cell.
71    #[inline]
72    pub fn new() -> Self {
73        Self { once: O::default(), data: UnsafeCell::new(MaybeUninit::uninit()) }
74    }
75
76    /// Gets the reference to the underlying value.
77    ///
78    /// Returns `None` if the cell is empty, or being initialized. This method
79    /// never blocks.
80    #[inline]
81    pub fn get(&self) -> Option<&T> {
82        if self.once.is_completed() {
83            // SAFETY: `self.once.is_completed()` returned `true`, which by the
84            // safety contract of `Once` guarantees `self.data` was initialized
85            // in `get_or_init` with an acquire memory barrier and will not be
86            // mutated again until `Drop`.
87            Some(unsafe { self.get_unchecked() })
88        } else {
89            None
90        }
91    }
92
93    /// Gets the contents of the cell, initializing it with `f` if the cell
94    /// was empty.
95    ///
96    /// Many threads may call `get_or_init` concurrently with different
97    /// initializing functions, but it is guaranteed that only one function
98    /// will be executed.
99    ///
100    /// # Panics
101    ///
102    /// Panics if `f` panics. If `f` panics, the cell is left uninitialized and
103    /// subsequent attempts to initialize the cell are expected to panic.
104    #[inline]
105    pub fn get_or_init<F: FnOnce() -> T>(&self, f: F) -> &T {
106        if let Some(value) = self.get() {
107            return value;
108        }
109        self.initialize(f);
110        // SAFETY: `self.initialize` only returns after `self.once.once(...)`
111        // has completed initializing `self.data`.
112        unsafe { self.get_unchecked() }
113    }
114
115    #[cold]
116    fn initialize<F: FnOnce() -> T>(&self, f: F) {
117        let slot = &self.data;
118        self.once.once(|| {
119            let value = f();
120            // SAFETY: `Once::once` guarantees exclusive access to this closure
121            // while initializing `self.data` for the first and only time.
122            let _: &mut T = unsafe { (*slot.get()).write(value) };
123        });
124    }
125
126    /// # Safety
127    ///
128    /// The cell must be initialized (`self.once.is_completed()` must be `true`).
129    #[inline]
130    unsafe fn get_unchecked(&self) -> &T {
131        // SAFETY: The caller guarantees that `self.data` has been initialized
132        // and no mutable references can exist until `Drop`.
133        unsafe { (*self.data.get()).assume_init_ref() }
134    }
135}
136
137impl<T: fmt::Debug, O: Once> fmt::Debug for OnceLock<T, O> {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        let mut d = f.debug_tuple("OnceLock");
140        let _: &mut fmt::DebugTuple<'_, '_> = match self.get() {
141            Some(v) => d.field(&v),
142            None => d.field(&format_args!("<uninit>")),
143        };
144        d.finish()
145    }
146}
147
148impl<T: PartialEq, O: Once> PartialEq for OnceLock<T, O> {
149    #[inline]
150    fn eq(&self, other: &Self) -> bool {
151        self.get() == other.get()
152    }
153}
154
155impl<T: Eq, O: Once> Eq for OnceLock<T, O> {}
156
157impl<T, O: Once> Drop for OnceLock<T, O> {
158    #[inline]
159    fn drop(&mut self) {
160        if self.once.is_completed() {
161            // SAFETY: `self.once.is_completed()` is `true`, so `self.data` is
162            // initialized, and `&mut self` guarantees exclusive access.
163            unsafe { self.data.get_mut().assume_init_drop() };
164        }
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171
172    use alloc::format;
173    use alloc::sync::Arc;
174    use alloc::vec::Vec;
175    use core::sync::atomic::{AtomicUsize, Ordering};
176    use std::thread;
177
178    #[derive(Default)]
179    struct StdOnce(std::sync::Once);
180
181    // SAFETY: `std::sync::Once` upholds all the guarantees required by `Once`.
182    unsafe impl Once for StdOnce {
183        fn once(&self, f: impl FnOnce()) {
184            let Self(once) = self;
185            once.call_once(f);
186        }
187
188        fn is_completed(&self) -> bool {
189            let Self(once) = self;
190            once.is_completed()
191        }
192    }
193
194    type TestOnceLock<T> = OnceLock<T, StdOnce>;
195
196    #[test]
197    fn once_lock_get_and_init() {
198        let lock = TestOnceLock::<u32>::default();
199        assert_eq!(lock.get(), None);
200        assert_eq!(format!("{lock:?}"), "OnceLock(<uninit>)");
201
202        let val = lock.get_or_init(|| 42);
203        assert_eq!(*val, 42);
204        assert_eq!(lock.get(), Some(&42));
205        assert_eq!(format!("{lock:?}"), "OnceLock(42)");
206
207        // Subsequent `get_or_init` calls must not run the closure.
208        let val2 = lock.get_or_init(|| panic!("initializer called twice"));
209        assert_eq!(*val2, 42);
210    }
211
212    #[test]
213    fn once_lock_partial_eq() {
214        let a = TestOnceLock::<u32>::new();
215        let b = TestOnceLock::<u32>::new();
216        assert_eq!(a, b);
217
218        assert_eq!(*a.get_or_init(|| 10), 10);
219        assert_ne!(a, b);
220
221        assert_eq!(*b.get_or_init(|| 10), 10);
222        assert_eq!(a, b);
223
224        let c = TestOnceLock::<u32>::new();
225        assert_eq!(*c.get_or_init(|| 20), 20);
226        assert_ne!(a, c);
227    }
228
229    #[test]
230    fn once_lock_drop_uninit() {
231        // Dropping an uninitialized `OnceLock` must not drop `T`.
232        struct DropBomb;
233        impl Drop for DropBomb {
234            fn drop(&mut self) {
235                panic!("drop called");
236            }
237        }
238        let _uninit = TestOnceLock::<DropBomb>::new();
239    }
240
241    #[test]
242    fn once_lock_drop_init() {
243        // Dropping an initialized `OnceLock` must drop `T` exactly once.
244        struct DropTracker(Arc<AtomicUsize>);
245        impl Drop for DropTracker {
246            fn drop(&mut self) {
247                let _: usize = self.0.fetch_add(1, Ordering::SeqCst);
248            }
249        }
250
251        let drops = Arc::new(AtomicUsize::new(0));
252        {
253            let init = TestOnceLock::<DropTracker>::new();
254            let _: &DropTracker = init.get_or_init(|| DropTracker(Arc::clone(&drops)));
255            assert_eq!(drops.load(Ordering::SeqCst), 0);
256        }
257        assert_eq!(drops.load(Ordering::SeqCst), 1);
258    }
259
260    #[test]
261    fn once_lock_multithreaded_init() {
262        const NUM_THREADS: usize = 8;
263
264        let lock = TestOnceLock::<usize>::new();
265        let init_calls = AtomicUsize::new(0);
266
267        let loaded = thread::scope(|s| {
268            let lock = &lock;
269            let init_calls = &init_calls;
270            let join = (0..NUM_THREADS)
271                .map(|i| {
272                    s.spawn(move || {
273                        lock.get_or_init(|| {
274                            let _: usize = init_calls.fetch_add(1, Ordering::SeqCst);
275                            i
276                        })
277                    })
278                })
279                .collect::<Vec<_>>();
280            // Every thread observes exactly the same value from the lock.
281            join.into_iter()
282                .fold(None, |acc, val| {
283                    let val = val.join().expect("thread complete");
284                    assert!(acc.is_none_or(|v| v == *val));
285                    Some(*val)
286                })
287                .expect("at least one item")
288        });
289
290        assert_eq!(init_calls.load(Ordering::SeqCst), 1);
291        assert_eq!(lock.get(), Some(&loaded));
292    }
293
294    #[test]
295    #[cfg(panic = "unwind")]
296    fn once_lock_panic() {
297        #[derive(Debug, PartialEq)]
298        struct DropBomb;
299        impl Drop for DropBomb {
300            fn drop(&mut self) {
301                panic!("drop called on uninitialized value");
302            }
303        }
304
305        let lock = TestOnceLock::<DropBomb>::new();
306
307        // The first attempt to initialize panics.
308        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
309            let _: &DropBomb = lock.get_or_init(|| panic!("initialization failure"));
310        }));
311        assert!(result.is_err());
312
313        // The lock remains uninitialized.
314        assert_eq!(lock.get(), None);
315
316        // Subsequent attempts to initialize must also panic.
317        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
318            let _: &DropBomb = lock.get_or_init(|| DropBomb);
319        }));
320        assert!(result.is_err());
321
322        // The lock remains uninitialized and dropping it must not drop `T`.
323        assert_eq!(lock.get(), None);
324    }
325}