Skip to main content

osiris/sync/
once.rs

1#![allow(dead_code)]
2
3use crate::hal;
4
5use core::cell::UnsafeCell;
6use core::mem::MaybeUninit;
7use core::ops::Deref;
8use core::sync::atomic::AtomicU8;
9use core::sync::atomic::Ordering;
10
11/// A synchronization primitive that can be used to block a thread until a value is ready.
12/// The procedure is as follows:
13/// 1. The Caller calls step(NOT_READY) to indicate that it is about to start the initialization process.
14/// 2. The Caller initializes the value.
15/// 3. The Caller calls step(IN_TRANSIT) to indicate that the value is ready.
16///    If step 1 fails, the value is already being initialized and the Caller must wait until is() returns true.
17pub struct Ready {
18    ready: AtomicU8,
19}
20
21impl Ready {
22    const READY: u8 = 2;
23    const IN_TRANSIT: u8 = 1;
24    const NOT_READY: u8 = 0;
25
26    /// Initializes a new Ready.
27    pub const fn new() -> Self {
28        Self {
29            ready: AtomicU8::new(0),
30        }
31    }
32
33    /// Move the Ready to the next state, if it is in state `from`.
34    pub fn step(&self, from: u8) -> bool {
35        self.forward(from, from + 1)
36    }
37
38    /// Move the Ready to state `to` if it is in state `from`.
39    fn forward(&self, _from: u8, _to: u8) -> bool {
40        self.ready
41            .compare_exchange(_from, _to, Ordering::AcqRel, Ordering::Acquire)
42            .is_ok()
43    }
44
45    /// Returns true if the value is ready.
46    pub fn is(&self) -> bool {
47        self.ready.load(Ordering::Acquire) == Self::READY
48    }
49}
50
51/// A synchronization primitive that represents a value that is initialized at most once.
52pub struct OnceCell<T> {
53    value: UnsafeCell<MaybeUninit<T>>,
54    init: Ready,
55}
56
57/// Safety:
58/// 1. A `value` is only written to atomically and once.
59/// 2. A `value` is only readable from after the initialization process is finished.
60/// 3. A `init` is only written and read from atomically.
61unsafe impl<T> Sync for OnceCell<T> {}
62
63impl<T> OnceCell<T> {
64    /// Initializes a new OnceCell.
65    pub const fn new() -> Self {
66        Self {
67            value: UnsafeCell::new(MaybeUninit::uninit()),
68            init: Ready::new(),
69        }
70    }
71
72    /// Returns a reference to the value if it is initialized.
73    pub fn get(&self) -> Option<&T> {
74        if self.init.is() {
75            // Safety:
76            // 1. By contract, is the value initialized if init.is() returns true.
77            // 2. No writes are allowed to the value after the initialization process is finished.
78            Some(unsafe { self.get_unchecked() })
79        } else {
80            None
81        }
82    }
83
84    /// Sets the value if it is not already initialized, and returns a reference to the value.
85    pub fn set_or_get(&self, value: T) -> &T {
86        if let Some(value) = self.set(value) {
87            value
88        } else {
89            // If we reach this point, initialization is already in progress.
90            while !self.init.is() {
91                hal::asm::nop!();
92            }
93            // Safety:
94            // 1. By contract, is the value initialized if init.is() returns true.
95            // 2. No writes are allowed to the value after the initialization process is finished.
96            unsafe { self.get_unchecked() }
97        }
98    }
99
100    /// Sets the value if it is not already initialized, and returns a reference to the value.
101    pub fn do_or_get<F>(&self, f: F) -> &T
102    where
103        F: FnOnce() -> T,
104    {
105        self.get_or_init(f)
106    }
107
108    /// Returns the value, initializing it with `f` if needed.
109    pub fn get_or_init<F>(&self, f: F) -> &T
110    where
111        F: FnOnce() -> T,
112    {
113        if let Some(value) = self.get() {
114            return value;
115        }
116
117        if self.init.step(Ready::NOT_READY) {
118            // Safety: We are now in the IN_TRANSIT state, so we are the only ones that can write to the value.
119            unsafe {
120                self.value.get().write(MaybeUninit::new(f()));
121            }
122
123            if self.init.step(Ready::IN_TRANSIT) {
124                // Safety: We are now in the READY state, so no writes can happen to the value.
125                return unsafe { self.get_unchecked() };
126            }
127
128            // By contract, only the thread that started the initialization process can finish it.
129            unreachable!();
130        }
131
132        // If we reach this point, initialization is already in progress.
133        while !self.init.is() {
134            hal::asm::nop!();
135        }
136
137        // Safety:
138        // 1. By contract, is the value initialized if init.is() returns true.
139        // 2. No writes are allowed to the value after the initialization process is finished.
140        unsafe { self.get_unchecked() }
141    }
142
143    /// Sets the value if it is not already initialized, returns a reference to the value if it was not set previously.
144    pub fn set(&self, value: T) -> Option<&T> {
145        if self.init.is() {
146            return None;
147        }
148
149        if self.init.step(Ready::NOT_READY) {
150            // Safety: We are now in the IN_TRANSIT state, so we are the only ones that can write to the value.
151            // We are also the only ones that can read from the value.
152            unsafe {
153                self.value.get().write(MaybeUninit::new(value));
154            }
155
156            if self.init.step(Ready::IN_TRANSIT) {
157                // Safety: We are now in the READY state, so no writes can happen to the value.
158                // 1. It is safe to create a immutable reference to the value.
159                // 2. We initialized the value, so it is safe to return a reference to it.
160                return Some(unsafe { self.get_unchecked() });
161            }
162
163            // By contract, only the thread that started the initialization process can finish it.
164            unreachable!();
165        }
166
167        None
168    }
169
170    /// Returns a reference to the value, unchecked.
171    ///
172    /// # Safety
173    /// Preconditions: The value must be initialized.
174    /// Postconditions: The value is returned.
175    unsafe fn get_unchecked(&self) -> &T {
176        unsafe { (*self.value.get()).assume_init_ref() }
177    }
178}
179
180/// A lazily initialized value.
181pub struct LazyLock<T, F = fn() -> T> {
182    cell: OnceCell<T>,
183    init: UnsafeCell<Option<F>>,
184}
185
186// Safety:
187// 1. `cell` synchronizes initialization and only exposes shared references once ready.
188// 2. `init` is taken only by the thread that transitions `cell` into initialization.
189unsafe impl<T: Sync, F: Send> Sync for LazyLock<T, F> {}
190unsafe impl<T: Send, F: Send> Send for LazyLock<T, F> {}
191
192impl<T, F> LazyLock<T, F> {
193    /// Creates a new LazyLock.
194    pub const fn new(f: F) -> Self {
195        Self {
196            cell: OnceCell::new(),
197            init: UnsafeCell::new(Some(f)),
198        }
199    }
200}
201
202impl<T, F> LazyLock<T, F>
203where
204    F: FnOnce() -> T,
205{
206    /// Returns the lazily initialized value.
207    pub fn force(this: &Self) -> &T {
208        this.cell.get_or_init(|| {
209            // Safety:
210            // 1. `get_or_init` calls this closure only for the thread that won initialization.
211            // 2. No other thread can access `init` after initialization starts.
212            let init = unsafe {
213                (*this.init.get())
214                    .take()
215                    .expect("LazyLock initializer missing")
216            };
217            init()
218        })
219    }
220}
221
222impl<T, F> Deref for LazyLock<T, F>
223where
224    F: FnOnce() -> T,
225{
226    type Target = T;
227
228    fn deref(&self) -> &Self::Target {
229        Self::force(self)
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    #[test]
238    fn once_cell_get_or_init_initializes_once() {
239        let cell = OnceCell::new();
240        let mut calls = 0usize;
241
242        assert_eq!(
243            *cell.get_or_init(|| {
244                calls += 1;
245                7
246            }),
247            7
248        );
249
250        assert_eq!(*cell.get_or_init(|| 11), 7);
251        assert_eq!(calls, 1);
252    }
253
254    #[test]
255    fn once_cell_do_or_get_does_not_call_initializer_when_ready() {
256        let cell = OnceCell::new();
257
258        assert_eq!(*cell.set_or_get(7), 7);
259        assert_eq!(*cell.do_or_get(|| panic!("initializer should not run")), 7);
260    }
261
262    #[test]
263    fn lazy_lock_initializes_on_first_access() {
264        let value = LazyLock::new(|| 7usize);
265
266        assert_eq!(*value, 7);
267        assert_eq!(*LazyLock::force(&value), 7);
268    }
269}