diff options
| author | Samuel Tardieu <[email protected]> | 2024-07-27 11:49:02 +0200 |
|---|---|---|
| committer | Samuel Tardieu <[email protected]> | 2024-07-29 11:04:59 +0200 |
| commit | 05e0f128462ebdacd3dffc27f74502913d782589 (patch) | |
| tree | b13be999acbf294a687f2d55165eea1cc9543073 /embassy-sync/src | |
| parent | 4e5a646f8b1905de014462f5f0441952ec7e209b (diff) | |
embassy-sync: add LazyLock
`LazyLock` is inspired by Rust 1.80.0's `std::sync::LazyLock` type.
Diffstat (limited to 'embassy-sync/src')
| -rw-r--r-- | embassy-sync/src/lazy_lock.rs | 84 | ||||
| -rw-r--r-- | embassy-sync/src/lib.rs | 1 |
2 files changed, 85 insertions, 0 deletions
diff --git a/embassy-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs new file mode 100644 index 000000000..2b5742491 --- /dev/null +++ b/embassy-sync/src/lazy_lock.rs | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | //! Synchronization primitive for initializing a value once, allowing others to get a reference to the value. | ||
| 2 | |||
| 3 | use core::cell::Cell; | ||
| 4 | use core::mem::MaybeUninit; | ||
| 5 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 6 | |||
| 7 | /// The `LazyLock` is a synchronization primitive that allows for | ||
| 8 | /// initializing a value once, and allowing others to obtain a | ||
| 9 | /// reference to the value. This is useful for lazy initialization of | ||
| 10 | /// a static value. | ||
| 11 | /// | ||
| 12 | /// # Example | ||
| 13 | /// ``` | ||
| 14 | /// use futures_executor::block_on; | ||
| 15 | /// use embassy_sync::lazy_lock::LazyLock; | ||
| 16 | /// | ||
| 17 | /// // Define a static value that will be lazily initialized | ||
| 18 | /// // at runtime at the first access. | ||
| 19 | /// static VALUE: LazyLock<u32> = LazyLock::new(|| 20); | ||
| 20 | /// | ||
| 21 | /// let reference = VALUE.get(); | ||
| 22 | /// assert_eq!(reference, &20); | ||
| 23 | /// ``` | ||
| 24 | pub struct LazyLock<T, F = fn() -> T> { | ||
| 25 | init: AtomicBool, | ||
| 26 | init_fn: Cell<Option<F>>, | ||
| 27 | data: Cell<MaybeUninit<T>>, | ||
| 28 | } | ||
| 29 | |||
| 30 | unsafe impl<T, F> Sync for LazyLock<T, F> {} | ||
| 31 | |||
| 32 | impl<T, F: FnOnce() -> T> LazyLock<T, F> { | ||
| 33 | /// Create a new uninitialized `StaticLock`. | ||
| 34 | pub const fn new(init_fn: F) -> Self { | ||
| 35 | Self { | ||
| 36 | init: AtomicBool::new(false), | ||
| 37 | init_fn: Cell::new(Some(init_fn)), | ||
| 38 | data: Cell::new(MaybeUninit::zeroed()), | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | /// Get a reference to the underlying value, initializing it if it | ||
| 43 | /// has not been done already. | ||
| 44 | #[inline] | ||
| 45 | pub fn get(&self) -> &T { | ||
| 46 | self.ensure_init_fast(); | ||
| 47 | unsafe { (*self.data.as_ptr()).assume_init_ref() } | ||
| 48 | } | ||
| 49 | |||
| 50 | /// Consume the `LazyLock`, returning the underlying value. The | ||
| 51 | /// initialization function will be called if it has not been | ||
| 52 | /// already. | ||
| 53 | #[inline] | ||
| 54 | pub fn into_inner(self) -> T { | ||
| 55 | self.ensure_init_fast(); | ||
| 56 | unsafe { self.data.into_inner().assume_init() } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Initialize the `LazyLock` if it has not been initialized yet. | ||
| 60 | /// This function is a fast track to [`Self::ensure_init`] | ||
| 61 | /// which does not require a critical section in most cases when | ||
| 62 | /// the value has been initialized already. | ||
| 63 | /// When this function returns, `self.data` is guaranteed to be | ||
| 64 | /// initialized and visible on the current core. | ||
| 65 | #[inline] | ||
| 66 | fn ensure_init_fast(&self) { | ||
| 67 | if !self.init.load(Ordering::Acquire) { | ||
| 68 | self.ensure_init(); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | /// Initialize the `LazyLock` if it has not been initialized yet. | ||
| 73 | /// When this function returns, `self.data` is guaranteed to be | ||
| 74 | /// initialized and visible on the current core. | ||
| 75 | fn ensure_init(&self) { | ||
| 76 | critical_section::with(|_| { | ||
| 77 | if !self.init.load(Ordering::Acquire) { | ||
| 78 | let init_fn = self.init_fn.take().unwrap(); | ||
| 79 | self.data.set(MaybeUninit::new(init_fn())); | ||
| 80 | self.init.store(true, Ordering::Release); | ||
| 81 | } | ||
| 82 | }); | ||
| 83 | } | ||
| 84 | } | ||
diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index a5eee8d02..014bf1d06 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs | |||
| @@ -12,6 +12,7 @@ mod ring_buffer; | |||
| 12 | 12 | ||
| 13 | pub mod blocking_mutex; | 13 | pub mod blocking_mutex; |
| 14 | pub mod channel; | 14 | pub mod channel; |
| 15 | pub mod lazy_lock; | ||
| 15 | pub mod mutex; | 16 | pub mod mutex; |
| 16 | pub mod once_lock; | 17 | pub mod once_lock; |
| 17 | pub mod pipe; | 18 | pub mod pipe; |
