diff options
| author | Ulf Lilleengen <[email protected]> | 2024-07-31 16:28:06 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-07-31 16:28:06 +0000 |
| commit | 32adddff9c7fc223853ada7e9ab5b7e06014a47c (patch) | |
| tree | 0ac5bf376b1fde60149d782d3dde46a5e53bc942 | |
| parent | 91f135e25bc5241c322c61ae641eea400b3857cb (diff) | |
| parent | 05562b92af53c742c6531ec616afd518112687b8 (diff) | |
Merge pull request #3221 from wllenyj/lazy-lock
embassy-sync: fix the data of LazyLock never drop
| -rw-r--r-- | embassy-sync/src/lazy_lock.rs | 88 |
1 files changed, 78 insertions, 10 deletions
diff --git a/embassy-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs index 2b5742491..18e3c2019 100644 --- a/embassy-sync/src/lazy_lock.rs +++ b/embassy-sync/src/lazy_lock.rs | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | //! Synchronization primitive for initializing a value once, allowing others to get a reference to the value. | 1 | //! Synchronization primitive for initializing a value once, allowing others to get a reference to the value. |
| 2 | 2 | ||
| 3 | use core::cell::Cell; | 3 | use core::cell::UnsafeCell; |
| 4 | use core::mem::MaybeUninit; | 4 | use core::mem::ManuallyDrop; |
| 5 | use core::sync::atomic::{AtomicBool, Ordering}; | 5 | use core::sync::atomic::{AtomicBool, Ordering}; |
| 6 | 6 | ||
| 7 | /// The `LazyLock` is a synchronization primitive that allows for | 7 | /// The `LazyLock` is a synchronization primitive that allows for |
| @@ -23,8 +23,12 @@ use core::sync::atomic::{AtomicBool, Ordering}; | |||
| 23 | /// ``` | 23 | /// ``` |
| 24 | pub struct LazyLock<T, F = fn() -> T> { | 24 | pub struct LazyLock<T, F = fn() -> T> { |
| 25 | init: AtomicBool, | 25 | init: AtomicBool, |
| 26 | init_fn: Cell<Option<F>>, | 26 | data: UnsafeCell<Data<T, F>>, |
| 27 | data: Cell<MaybeUninit<T>>, | 27 | } |
| 28 | |||
| 29 | union Data<T, F> { | ||
| 30 | value: ManuallyDrop<T>, | ||
| 31 | f: ManuallyDrop<F>, | ||
| 28 | } | 32 | } |
| 29 | 33 | ||
| 30 | unsafe impl<T, F> Sync for LazyLock<T, F> {} | 34 | unsafe impl<T, F> Sync for LazyLock<T, F> {} |
| @@ -34,8 +38,9 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> { | |||
| 34 | pub const fn new(init_fn: F) -> Self { | 38 | pub const fn new(init_fn: F) -> Self { |
| 35 | Self { | 39 | Self { |
| 36 | init: AtomicBool::new(false), | 40 | init: AtomicBool::new(false), |
| 37 | init_fn: Cell::new(Some(init_fn)), | 41 | data: UnsafeCell::new(Data { |
| 38 | data: Cell::new(MaybeUninit::zeroed()), | 42 | f: ManuallyDrop::new(init_fn), |
| 43 | }), | ||
| 39 | } | 44 | } |
| 40 | } | 45 | } |
| 41 | 46 | ||
| @@ -44,7 +49,7 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> { | |||
| 44 | #[inline] | 49 | #[inline] |
| 45 | pub fn get(&self) -> &T { | 50 | pub fn get(&self) -> &T { |
| 46 | self.ensure_init_fast(); | 51 | self.ensure_init_fast(); |
| 47 | unsafe { (*self.data.as_ptr()).assume_init_ref() } | 52 | unsafe { &(*self.data.get()).value } |
| 48 | } | 53 | } |
| 49 | 54 | ||
| 50 | /// Consume the `LazyLock`, returning the underlying value. The | 55 | /// Consume the `LazyLock`, returning the underlying value. The |
| @@ -53,7 +58,10 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> { | |||
| 53 | #[inline] | 58 | #[inline] |
| 54 | pub fn into_inner(self) -> T { | 59 | pub fn into_inner(self) -> T { |
| 55 | self.ensure_init_fast(); | 60 | self.ensure_init_fast(); |
| 56 | unsafe { self.data.into_inner().assume_init() } | 61 | let this = ManuallyDrop::new(self); |
| 62 | let data = unsafe { core::ptr::read(&this.data) }.into_inner(); | ||
| 63 | |||
| 64 | ManuallyDrop::into_inner(unsafe { data.value }) | ||
| 57 | } | 65 | } |
| 58 | 66 | ||
| 59 | /// Initialize the `LazyLock` if it has not been initialized yet. | 67 | /// Initialize the `LazyLock` if it has not been initialized yet. |
| @@ -75,10 +83,70 @@ impl<T, F: FnOnce() -> T> LazyLock<T, F> { | |||
| 75 | fn ensure_init(&self) { | 83 | fn ensure_init(&self) { |
| 76 | critical_section::with(|_| { | 84 | critical_section::with(|_| { |
| 77 | if !self.init.load(Ordering::Acquire) { | 85 | if !self.init.load(Ordering::Acquire) { |
| 78 | let init_fn = self.init_fn.take().unwrap(); | 86 | let data = unsafe { &mut *self.data.get() }; |
| 79 | self.data.set(MaybeUninit::new(init_fn())); | 87 | let f = unsafe { ManuallyDrop::take(&mut data.f) }; |
| 88 | let value = f(); | ||
| 89 | data.value = ManuallyDrop::new(value); | ||
| 90 | |||
| 80 | self.init.store(true, Ordering::Release); | 91 | self.init.store(true, Ordering::Release); |
| 81 | } | 92 | } |
| 82 | }); | 93 | }); |
| 83 | } | 94 | } |
| 84 | } | 95 | } |
| 96 | |||
| 97 | impl<T, F> Drop for LazyLock<T, F> { | ||
| 98 | fn drop(&mut self) { | ||
| 99 | if self.init.load(Ordering::Acquire) { | ||
| 100 | unsafe { ManuallyDrop::drop(&mut self.data.get_mut().value) }; | ||
| 101 | } else { | ||
| 102 | unsafe { ManuallyDrop::drop(&mut self.data.get_mut().f) }; | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | #[cfg(test)] | ||
| 108 | mod tests { | ||
| 109 | use core::sync::atomic::{AtomicU32, Ordering}; | ||
| 110 | |||
| 111 | use super::*; | ||
| 112 | |||
| 113 | #[test] | ||
| 114 | fn test_lazy_lock() { | ||
| 115 | static VALUE: LazyLock<u32> = LazyLock::new(|| 20); | ||
| 116 | let reference = VALUE.get(); | ||
| 117 | assert_eq!(reference, &20); | ||
| 118 | } | ||
| 119 | #[test] | ||
| 120 | fn test_lazy_lock_into_inner() { | ||
| 121 | let lazy: LazyLock<u32> = LazyLock::new(|| 20); | ||
| 122 | let value = lazy.into_inner(); | ||
| 123 | assert_eq!(value, 20); | ||
| 124 | } | ||
| 125 | |||
| 126 | static DROP_CHECKER: AtomicU32 = AtomicU32::new(0); | ||
| 127 | struct DropCheck; | ||
| 128 | |||
| 129 | impl Drop for DropCheck { | ||
| 130 | fn drop(&mut self) { | ||
| 131 | DROP_CHECKER.fetch_add(1, Ordering::Acquire); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | #[test] | ||
| 136 | fn test_lazy_drop() { | ||
| 137 | let lazy: LazyLock<DropCheck> = LazyLock::new(|| DropCheck); | ||
| 138 | assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 0); | ||
| 139 | lazy.get(); | ||
| 140 | drop(lazy); | ||
| 141 | assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 1); | ||
| 142 | |||
| 143 | let dropper = DropCheck; | ||
| 144 | let lazy_fn: LazyLock<u32, _> = LazyLock::new(move || { | ||
| 145 | let _a = dropper; | ||
| 146 | 20 | ||
| 147 | }); | ||
| 148 | assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 1); | ||
| 149 | drop(lazy_fn); | ||
| 150 | assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 2); | ||
| 151 | } | ||
| 152 | } | ||
