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*/