Skip to main content

relibc/sync/
once.rs

1use super::AttemptStatus;
2use crate::platform::types::*;
3use core::{
4    cell::UnsafeCell,
5    mem::MaybeUninit,
6    sync::atomic::{AtomicI32 as AtomicInt, Ordering},
7};
8
9const UNINITIALIZED: c_int = 0;
10const INITIALIZING: c_int = 1;
11const WAITING: c_int = 2;
12const INITIALIZED: c_int = 3;
13
14pub struct Once<T> {
15    status: AtomicInt,
16    data: UnsafeCell<MaybeUninit<T>>,
17}
18
19// SAFETY:
20//
21// Sending a Once is the same as sending a (wrapped) T.
22unsafe impl<T: Send> Send for Once<T> {}
23
24// SAFETY:
25//
26// For Once to be shared between threads without being unsound, only call_once needs to be safe, at
27// the moment.
28//
29// Send requirement: the thread that gets to run the initializer function, will put a T in the cell
30// which can then be accessed by other threads, thus T needs to be send.
31//
32// Sync requirement: after call_once has been called, it returns the value via &T, which naturally
33// forces T to be Sync.
34unsafe impl<T: Send + Sync> Sync for Once<T> {}
35
36impl<T> Once<T> {
37    pub const fn new() -> Self {
38        Self {
39            status: AtomicInt::new(UNINITIALIZED),
40            data: UnsafeCell::new(MaybeUninit::uninit()),
41        }
42    }
43    pub fn call_once(&self, constructor: impl FnOnce() -> T) -> &T {
44        match self.status.compare_exchange(
45            UNINITIALIZED,
46            INITIALIZING,
47            // SAFETY: Success ordering: if the CAS succeeds, we technically need no
48            // synchronization besides the Release store to INITIALIZED, and Acquire here forbids
49            // possible loads in f() to be re-ordered before this CAS. One could argue whether or
50            // not that is reasonable, but the main point is that the success ordering must be at
51            // least as strong as the failure ordering.
52            Ordering::Acquire,
53            // SAFETY: Failure ordering: if the CAS fails, and status was INITIALIZING | WAITING,
54            // then Relaxed is sufficient, as it will have to be Acquire-loaded again later. If
55            // INITIALIZED is encountered however, it will nonatomically read the value in the
56            // Cell, which necessitates Acquire.
57            Ordering::Acquire, // TODO: On archs where this matters, use Relaxed and core::sync::atomic::fence?
58        ) {
59            Ok(_must_be_uninit) => {
60                // We now have exclusive access to the cell, let's initiate things!
61                unsafe { self.data.get().cast::<T>().write(constructor()) };
62
63                // Mark the data as initialized
64                if self.status.swap(INITIALIZED, Ordering::Release) == WAITING {
65                    // At least one thread is waiting on this to finish
66                    crate::sync::futex_wake(&self.status, i32::MAX);
67                }
68            }
69            Err(INITIALIZING) | Err(WAITING) => crate::sync::wait_until_generic(
70                &self.status,
71                // SAFETY: An Acquire load is necessary for the nonatomic store by the thread
72                // running the constructor, to become visible.
73                |status| match status.load(Ordering::Acquire) {
74                    WAITING => AttemptStatus::Waiting,
75                    INITIALIZED => AttemptStatus::Desired,
76                    _ => AttemptStatus::Other,
77                },
78                // SAFETY: Double-Acquire is necessary here as well, because if the CAS fails and
79                // it was INITIALIZED, the nonatomic write by the constructor thread, must be
80                // visible.
81                |status| match status
82                    .compare_exchange_weak(
83                        INITIALIZING,
84                        WAITING,
85                        Ordering::Acquire,
86                        Ordering::Acquire,
87                    )
88                    .unwrap_or_else(|e| e)
89                {
90                    WAITING => AttemptStatus::Waiting,
91                    INITIALIZED => AttemptStatus::Desired,
92                    _ => AttemptStatus::Other,
93                },
94                WAITING,
95            ),
96            Err(INITIALIZED) => (),
97
98            // TODO: Only for debug builds?
99            Err(_) => unreachable!("invalid state for Once<T>"),
100        }
101
102        // At this point the data must be initialized!
103        unsafe { (&*self.data.get()).assume_init_ref() }
104    }
105}
106impl<T> Default for Once<T> {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111// TODO: Drop doesn't work well in const fn, instead use a wrapper for relibc Rust code that adds
112// Drop, and don't use that wrapper when writing the header file impls.
113/*
114impl<T> Drop for Once<T> {
115    fn drop(&mut self) {
116        unsafe {
117            if *self.status.get_mut() == INITIALIZED {
118                // SAFETY: It must be initialized, because of the above condition.
119                self.data.get_mut().assume_init_drop();
120            }
121        }
122    }
123}
124*/