Next Generation WASM Microkernel Operating System
1// Copyright 2025 Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use crate::loom::{AtomicU8, Ordering};
9use core::mem;
10use util::loom_const_fn;
11
12/// No initialization has run yet, and no thread is currently using the Once.
13const STATUS_INCOMPLETE: u8 = 0;
14/// Some thread has previously attempted to initialize the Once, but it panicked,
15/// so the Once is now poisoned. There are no other threads currently accessing
16/// this Once.
17const STATUS_POISONED: u8 = 1;
18/// Some thread is currently attempting to run initialization. It may succeed,
19/// so all future threads need to wait for it to finish.
20const STATUS_RUNNING: u8 = 2;
21/// Initialization has completed and all future calls should finish immediately.
22const STATUS_COMPLETE: u8 = 4;
23
24pub enum ExclusiveState {
25 Incomplete,
26 Poisoned,
27 Complete,
28}
29
30/// A synchronization primitive for running one-time global initialization.
31pub struct Once {
32 status: AtomicU8,
33}
34
35impl Once {
36 loom_const_fn! {
37 #[inline]
38 #[must_use]
39 pub const fn new() -> Once {
40 Once {
41 status: AtomicU8::new(STATUS_INCOMPLETE),
42 }
43 }
44 }
45
46 #[inline]
47 pub fn is_completed(&self) -> bool {
48 self.status.load(Ordering::Acquire) == STATUS_COMPLETE
49 }
50
51 pub fn state(&mut self) -> ExclusiveState {
52 self.status.with_mut(|status| match *status {
53 STATUS_INCOMPLETE => ExclusiveState::Incomplete,
54 STATUS_POISONED => ExclusiveState::Poisoned,
55 STATUS_COMPLETE => ExclusiveState::Complete,
56 _ => unreachable!("invalid Once state"),
57 })
58 }
59
60 /// # Panics
61 ///
62 /// Panics if the closure panics.
63 #[inline]
64 #[track_caller]
65 pub fn call_once<F>(&self, f: F)
66 where
67 F: FnOnce(),
68 {
69 // Fast path check
70 if self.is_completed() {
71 return;
72 }
73
74 let mut f = Some(f);
75 #[allow(tail_expr_drop_order, reason = "")]
76 self.call(&mut || f.take().unwrap()());
77 }
78
79 #[cold]
80 #[track_caller]
81 fn call(&self, f: &mut impl FnMut()) {
82 loop {
83 let xchg = self.status.compare_exchange(
84 STATUS_INCOMPLETE,
85 STATUS_RUNNING,
86 Ordering::Acquire,
87 Ordering::Acquire,
88 );
89
90 match xchg {
91 Ok(_) => {
92 let panic_guard = PanicGuard {
93 status: &self.status,
94 };
95
96 f();
97
98 mem::forget(panic_guard);
99
100 self.status.store(STATUS_COMPLETE, Ordering::Release);
101
102 return;
103 }
104 Err(STATUS_COMPLETE) => return,
105 Err(STATUS_RUNNING) => self.wait(),
106 Err(STATUS_POISONED) => {
107 // Panic to propagate the poison.
108 panic!("Once instance has previously been poisoned");
109 }
110 _ => unreachable!("state is never set to invalid values"),
111 }
112 }
113 }
114
115 fn poll(&self) -> bool {
116 match self.status.load(Ordering::Acquire) {
117 STATUS_INCOMPLETE | STATUS_RUNNING => false,
118 STATUS_COMPLETE => true,
119 STATUS_POISONED => panic!("Once poisoned by panic"),
120 _ => unreachable!(),
121 }
122 }
123
124 pub fn wait(&self) {
125 while !self.poll() {
126 #[cfg(loom)]
127 crate::loom::thread::yield_now();
128 core::hint::spin_loop();
129 }
130 }
131}
132
133impl Default for Once {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139struct PanicGuard<'a> {
140 status: &'a AtomicU8,
141}
142
143impl Drop for PanicGuard<'_> {
144 fn drop(&mut self) {
145 self.status.store(STATUS_POISONED, Ordering::Relaxed);
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use crate::loom::thread;
153 use std::sync::mpsc::channel;
154
155 #[test]
156 fn smoke_once() {
157 static O: std::sync::LazyLock<Once> = std::sync::LazyLock::new(|| Once::new());
158 let mut a = 0;
159 O.call_once(|| a += 1);
160 assert_eq!(a, 1);
161 O.call_once(|| a += 1);
162 assert_eq!(a, 1);
163 }
164
165 #[test]
166 fn stampede_once() {
167 crate::loom::model(|| {
168 static O: std::sync::LazyLock<Once> = std::sync::LazyLock::new(|| Once::new());
169 static mut RUN: bool = false;
170
171 const MAX_THREADS: usize = 4;
172
173 let (tx, rx) = channel();
174 for _ in 0..MAX_THREADS {
175 let tx = tx.clone();
176 thread::spawn(move || {
177 // for _ in 0..2 {
178 // thread::yield_now()
179 // }
180 unsafe {
181 O.call_once(|| {
182 assert!(!RUN);
183 RUN = true;
184 });
185 assert!(RUN);
186 }
187 tx.send(()).unwrap();
188 });
189 }
190
191 unsafe {
192 O.call_once(|| {
193 assert!(!RUN);
194 RUN = true;
195 });
196 assert!(RUN);
197 }
198
199 for _ in 0..MAX_THREADS {
200 rx.recv().unwrap();
201 }
202 })
203 }
204
205 #[cfg(not(loom))]
206 #[test]
207 fn wait() {
208 use crate::loom::{AtomicBool, Ordering};
209
210 for _ in 0..50 {
211 let val = AtomicBool::new(false);
212 let once = Once::new();
213
214 thread::scope(|s| {
215 for _ in 0..4 {
216 s.spawn(|| {
217 once.wait();
218 assert!(val.load(Ordering::Relaxed));
219 });
220 }
221
222 once.call_once(|| val.store(true, Ordering::Relaxed));
223 });
224 }
225 }
226}