diff options
Diffstat (limited to 'embassy-sync')
| -rw-r--r-- | embassy-sync/src/lib.rs | 1 | ||||
| -rw-r--r-- | embassy-sync/src/once_lock.rs | 237 |
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; | |||
| 13 | pub mod blocking_mutex; | 13 | pub mod blocking_mutex; |
| 14 | pub mod channel; | 14 | pub mod channel; |
| 15 | pub mod mutex; | 15 | pub mod mutex; |
| 16 | pub mod once_lock; | ||
| 16 | pub mod pipe; | 17 | pub mod pipe; |
| 17 | pub mod priority_channel; | 18 | pub mod priority_channel; |
| 18 | pub mod pubsub; | 19 | pub 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 | |||
| 3 | use core::cell::Cell; | ||
| 4 | use core::future::poll_fn; | ||
| 5 | use core::mem::MaybeUninit; | ||
| 6 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 7 | use 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 | /// ``` | ||
| 40 | pub struct OnceLock<T> { | ||
| 41 | init: AtomicBool, | ||
| 42 | data: Cell<MaybeUninit<T>>, | ||
| 43 | } | ||
| 44 | |||
| 45 | unsafe impl<T> Sync for OnceLock<T> {} | ||
| 46 | |||
| 47 | impl<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)] | ||
| 155 | mod 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 | } | ||
