aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy-sync/src/lib.rs1
-rw-r--r--embassy-sync/src/once_lock.rs237
2 files changed, 238 insertions, 0 deletions
diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs
index d88c76db5..61b173e80 100644
--- a/embassy-sync/src/lib.rs
+++ b/embassy-sync/src/lib.rs
@@ -13,6 +13,7 @@ mod ring_buffer;
13pub mod blocking_mutex; 13pub mod blocking_mutex;
14pub mod channel; 14pub mod channel;
15pub mod mutex; 15pub mod mutex;
16pub mod once_lock;
16pub mod pipe; 17pub mod pipe;
17pub mod priority_channel; 18pub mod priority_channel;
18pub mod pubsub; 19pub mod pubsub;
diff --git a/embassy-sync/src/once_lock.rs b/embassy-sync/src/once_lock.rs
new file mode 100644
index 000000000..f83577a6d
--- /dev/null
+++ b/embassy-sync/src/once_lock.rs
@@ -0,0 +1,237 @@
1//! Syncronization primitive for initializing a value once, allowing others to await a reference to the value.
2
3use core::cell::Cell;
4use core::future::poll_fn;
5use core::mem::MaybeUninit;
6use core::sync::atomic::{AtomicBool, Ordering};
7use core::task::Poll;
8
9/// The `OnceLock` is a synchronization primitive that allows for
10/// initializing a value once, and allowing others to `.await` a
11/// reference to the value. This is useful for lazy initialization of
12/// a static value.
13///
14/// **Note**: this implementation uses a busy loop to poll the value,
15/// which is not as efficient as registering a dedicated `Waker`.
16/// However, the if the usecase for is to initialize a static variable
17/// relatively early in the program life cycle, it should be fine.
18///
19/// # Example
20/// ```
21/// use futures_executor::block_on;
22/// use embassy_sync::once_lock::OnceLock;
23///
24/// // Define a static value that will be lazily initialized
25/// static VALUE: OnceLock<u32> = OnceLock::new();
26///
27/// let f = async {
28///
29/// // Initialize the value
30/// let reference = VALUE.get_or_init(|| 20);
31/// assert_eq!(reference, &20);
32///
33/// // Wait for the value to be initialized
34/// // and get a static reference it
35/// assert_eq!(VALUE.get().await, &20);
36///
37/// };
38/// block_on(f)
39/// ```
40pub struct OnceLock<T> {
41 init: AtomicBool,
42 data: Cell<MaybeUninit<T>>,
43}
44
45unsafe impl<T> Sync for OnceLock<T> {}
46
47impl<T> OnceLock<T> {
48 /// Create a new uninitialized `OnceLock`.
49 pub const fn new() -> Self {
50 Self {
51 init: AtomicBool::new(false),
52 data: Cell::new(MaybeUninit::zeroed()),
53 }
54 }
55
56 /// Get a reference to the underlying value, waiting for it to be set.
57 /// If the value is already set, this will return immediately.
58 pub async fn get(&self) -> &T {
59
60 poll_fn(|cx| match self.try_get() {
61 Some(data) => Poll::Ready(data),
62 None => {
63 cx.waker().wake_by_ref();
64 Poll::Pending
65 }
66 })
67 .await
68 }
69
70 /// Try to get a reference to the underlying value if it exists.
71 pub fn try_get(&self) -> Option<&T> {
72 if self.init.load(Ordering::Relaxed) {
73 Some(unsafe { self.get_ref_unchecked() })
74 } else {
75 None
76 }
77 }
78
79 /// Set the underlying value. If the value is already set, this will return an error with the given value.
80 pub fn init(&self, value: T) -> Result<(), T> {
81 // Critical section is required to ensure that the value is
82 // not simultaniously initialized elsewhere at the same time.
83 critical_section::with(|_| {
84 // If the value is not set, set it and return Ok.
85 if !self.init.load(Ordering::Relaxed) {
86 self.data.set(MaybeUninit::new(value));
87 self.init.store(true, Ordering::Relaxed);
88 Ok(())
89
90 // Otherwise return an error with the given value.
91 } else {
92 Err(value)
93 }
94 })
95 }
96
97 /// Get a reference to the underlying value, initializing it if it does not exist.
98 pub fn get_or_init<F>(&self, f: F) -> &T
99 where
100 F: FnOnce() -> T,
101 {
102 // Critical section is required to ensure that the value is
103 // not simultaniously initialized elsewhere at the same time.
104 critical_section::with(|_| {
105 // If the value is not set, set it.
106 if !self.init.load(Ordering::Relaxed) {
107 self.data.set(MaybeUninit::new(f()));
108 self.init.store(true, Ordering::Relaxed);
109 }
110 });
111
112 // Return a reference to the value.
113 unsafe { self.get_ref_unchecked() }
114 }
115
116 /// Consume the `OnceLock`, returning the underlying value if it was initialized.
117 pub fn into_inner(self) -> Option<T> {
118 if self.init.load(Ordering::Relaxed) {
119 Some(unsafe { self.data.into_inner().assume_init() })
120 } else {
121 None
122 }
123 }
124
125 /// Take the underlying value if it was initialized, uninitializing the `OnceLock` in the process.
126 pub fn take(&mut self) -> Option<T> {
127 // If the value is set, uninitialize the lock and return the value.
128 critical_section::with(|_| {
129 if self.init.load(Ordering::Relaxed) {
130 let val = unsafe { self.data.replace(MaybeUninit::zeroed()).assume_init() };
131 self.init.store(false, Ordering::Relaxed);
132 Some(val)
133
134 // Otherwise return None.
135 } else {
136 None
137 }
138 })
139 }
140
141 /// Check if the value has been set.
142 pub fn is_set(&self) -> bool {
143 self.init.load(Ordering::Relaxed)
144 }
145
146 /// Get a reference to the underlying value.
147 /// # Safety
148 /// Must only be used if a value has been set.
149 unsafe fn get_ref_unchecked(&self) -> &T {
150 (*self.data.as_ptr()).assume_init_ref()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn once_lock() {
160 let lock = OnceLock::new();
161 assert_eq!(lock.try_get(), None);
162 assert_eq!(lock.is_set(), false);
163
164 let v = 42;
165 assert_eq!(lock.init(v), Ok(()));
166 assert_eq!(lock.is_set(), true);
167 assert_eq!(lock.try_get(), Some(&v));
168 assert_eq!(lock.try_get(), Some(&v));
169
170 let v = 43;
171 assert_eq!(lock.init(v), Err(v));
172 assert_eq!(lock.is_set(), true);
173 assert_eq!(lock.try_get(), Some(&42));
174 }
175
176 #[test]
177 fn once_lock_get_or_init() {
178 let lock = OnceLock::new();
179 assert_eq!(lock.try_get(), None);
180 assert_eq!(lock.is_set(), false);
181
182 let v = lock.get_or_init(|| 42);
183 assert_eq!(v, &42);
184 assert_eq!(lock.is_set(), true);
185 assert_eq!(lock.try_get(), Some(&42));
186
187 let v = lock.get_or_init(|| 43);
188 assert_eq!(v, &42);
189 assert_eq!(lock.is_set(), true);
190 assert_eq!(lock.try_get(), Some(&42));
191 }
192
193 #[test]
194 fn once_lock_static() {
195 static LOCK: OnceLock<i32> = OnceLock::new();
196
197 let v: &'static i32 = LOCK.get_or_init(|| 42);
198 assert_eq!(v, &42);
199
200 let v: &'static i32 = LOCK.get_or_init(|| 43);
201 assert_eq!(v, &42);
202 }
203
204 #[futures_test::test]
205 async fn once_lock_async() {
206 static LOCK: OnceLock<i32> = OnceLock::new();
207
208 assert!(LOCK.init(42).is_ok());
209
210 let v: &'static i32 = LOCK.get().await;
211 assert_eq!(v, &42);
212 }
213
214 #[test]
215 fn once_lock_into_inner() {
216 let lock: OnceLock<i32> = OnceLock::new();
217
218 let v = lock.get_or_init(|| 42);
219 assert_eq!(v, &42);
220
221 assert_eq!(lock.into_inner(), Some(42));
222 }
223
224 #[test]
225 fn once_lock_take_init() {
226 let mut lock: OnceLock<i32> = OnceLock::new();
227
228 assert_eq!(lock.get_or_init(|| 42), &42);
229 assert_eq!(lock.is_set(), true);
230
231 assert_eq!(lock.take(), Some(42));
232 assert_eq!(lock.is_set(), false);
233
234 assert_eq!(lock.get_or_init(|| 43), &43);
235 assert_eq!(lock.is_set(), true);
236 }
237}