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}