Skip to content

Commit 37df2af

Browse files
committed
Global: use the wheel, dont reinvent it
changed to wrap Mutex<OnceLock>
1 parent 7eec09c commit 37df2af

File tree

1 file changed

+41
-112
lines changed

1 file changed

+41
-112
lines changed

godot-ffi/src/global.rs

Lines changed: 41 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use std::sync::{Mutex, MutexGuard, TryLockError};
8+
use std::sync::{Mutex, OnceLock, PoisonError, TryLockError};
99

1010
/// Ergonomic global variables.
1111
///
@@ -23,9 +23,10 @@ use std::sync::{Mutex, MutexGuard, TryLockError};
2323
///
2424
/// There are two `const` methods for construction: [`new()`](Self::new) and [`default()`](Self::default).
2525
/// For access, you should primarily use [`lock()`](Self::lock). There is also [`try_lock()`](Self::try_lock) for special cases.
26-
pub struct Global<T> {
26+
pub struct Global<T, F = fn() -> T> {
2727
// When needed, this could be changed to use RwLock and separate read/write guards.
28-
value: Mutex<InitState<T>>,
28+
value: Mutex<OnceLock<T>>,
29+
init_fn: F,
2930
}
3031

3132
impl<T> Global<T> {
@@ -35,7 +36,8 @@ impl<T> Global<T> {
3536
pub const fn new(init_fn: fn() -> T) -> Self {
3637
// Note: could be generalized to F: FnOnce() -> T + Send. See also once_cell::Lazy<T, F>.
3738
Self {
38-
value: Mutex::new(InitState::Pending(init_fn)),
39+
value: Mutex::new(OnceLock::new()),
40+
init_fn,
3941
}
4042
}
4143

@@ -57,72 +59,30 @@ impl<T> Global<T> {
5759
/// If the initialization function panics. Once that happens, the global is considered poisoned and all future calls to `lock()` will panic.
5860
/// This can currently not be recovered from.
5961
pub fn lock(&self) -> GlobalGuard<'_, T> {
60-
let mutex_guard = self
61-
.value
62-
.lock()
63-
.expect("Global<T> poisoned; a thread has panicked while holding a lock to it");
64-
65-
let guard = Self::ensure_init(mutex_guard, true)
66-
.unwrap_or_else(|| panic!("previous Global<T> initialization failed due to panic"));
67-
68-
guard
62+
let guard = self.value.lock().unwrap_or_else(PoisonError::into_inner);
63+
guard.get_or_init(self.init_fn);
64+
// SAFETY: above line guarantees init
65+
unsafe { GlobalGuard::new_unchecked(guard) }
6966
}
7067

71-
/// Non-panicking access with error introspection.
68+
/// Non-blocking access with error introspection.
7269
pub fn try_lock(&self) -> Result<GlobalGuard<'_, T>, GlobalLockError<'_, T>> {
73-
let guard = match self.value.try_lock() {
74-
Ok(mutex_guard) => Self::ensure_init(mutex_guard, false),
75-
Err(TryLockError::WouldBlock) => {
76-
return Err(GlobalLockError::WouldBlock);
77-
}
78-
Err(TryLockError::Poisoned(poisoned)) => {
79-
return Err(GlobalLockError::Poisoned {
80-
// We can likely use `new_unchecked` here, but verifying that it's safe would need somewhat tricky reasoning.
81-
// Since this error condition isn't very common, it is likely not very important to optimize access to the value here.
82-
// Especially since most users will likely not want to access it anyway.
83-
circumvent: GlobalGuard::new(poisoned.into_inner())
84-
.expect("Poisoned global guard should always be initialized"),
85-
});
86-
}
87-
};
88-
89-
guard.ok_or(GlobalLockError::InitFailed)
90-
}
91-
92-
fn ensure_init(
93-
mut mutex_guard: MutexGuard<'_, InitState<T>>,
94-
may_panic: bool,
95-
) -> Option<GlobalGuard<'_, T>> {
96-
let init_fn = match &mut *mutex_guard {
97-
InitState::Initialized(_) => {
98-
// SAFETY: `mutex_guard` is `Initialized`.
99-
return Some(unsafe { GlobalGuard::new_unchecked(mutex_guard) });
100-
}
101-
InitState::Failed => {
102-
return None;
103-
}
104-
InitState::Pending(init_fn) => init_fn,
105-
};
106-
107-
// Unwinding should be safe here, as there is no unsafe code relying on it.
108-
let init_fn = std::panic::AssertUnwindSafe(init_fn);
109-
match std::panic::catch_unwind(init_fn) {
110-
Ok(value) => *mutex_guard = InitState::Initialized(value),
111-
Err(e) => {
112-
eprintln!("panic during Global<T> initialization");
113-
*mutex_guard = InitState::Failed;
114-
115-
if may_panic {
116-
std::panic::resume_unwind(e);
117-
} else {
118-
// Note: this currently swallows panic.
119-
return None;
70+
let guard = self.value.try_lock().map_err(|x| match x {
71+
TryLockError::Poisoned(x) => {
72+
let g = x.into_inner();
73+
match std::panic::catch_unwind(|| g.get_or_init(self.init_fn)) {
74+
Ok(_) => GlobalLockError::Poisoned {
75+
// SAFETY: see get_or_init.
76+
circumvent: unsafe { GlobalGuard::new_unchecked(g) },
77+
},
78+
Err(_) => GlobalLockError::InitFailed,
12079
}
12180
}
122-
};
123-
124-
// SAFETY: `mutex_guard` was either set to `Initialized` above, or we returned from the function.
125-
Some(unsafe { GlobalGuard::new_unchecked(mutex_guard) })
81+
TryLockError::WouldBlock => GlobalLockError::WouldBlock,
82+
})?;
83+
std::panic::catch_unwind(|| guard.get_or_init(self.init_fn))
84+
.map_err(|_| GlobalLockError::InitFailed)?;
85+
Ok(unsafe { GlobalGuard::new_unchecked(guard) })
12686
}
12787
}
12888

@@ -131,57 +91,50 @@ impl<T> Global<T> {
13191

13292
// Encapsulate private fields.
13393
mod global_guard {
134-
use std::ops::{Deref, DerefMut};
135-
use std::sync::MutexGuard;
136-
137-
use super::InitState;
94+
use std::{
95+
ops::{Deref, DerefMut},
96+
sync::{MutexGuard, OnceLock},
97+
};
13898

13999
/// Guard that temporarily gives access to a `Global<T>`'s inner value.
140100
pub struct GlobalGuard<'a, T> {
141-
// Safety invariant: Is `Initialized`.
142-
mutex_guard: MutexGuard<'a, InitState<T>>,
101+
mutex_guard: MutexGuard<'a, OnceLock<T>>,
143102
}
144103

145104
impl<'a, T> GlobalGuard<'a, T> {
146-
pub(super) fn new(mutex_guard: MutexGuard<'a, InitState<T>>) -> Option<Self> {
147-
match &*mutex_guard {
148-
InitState::Initialized(_) => Some(Self { mutex_guard }),
105+
pub(super) fn new(mutex_guard: MutexGuard<'a, OnceLock<T>>) -> Option<Self> {
106+
match mutex_guard.get() {
107+
Some(_) => Some(Self { mutex_guard }),
149108
_ => None,
150109
}
151110
}
152111

153112
/// # Safety
154113
///
155-
/// The value must be `Initialized`.
156-
pub(super) unsafe fn new_unchecked(mutex_guard: MutexGuard<'a, InitState<T>>) -> Self {
157-
debug_assert!(matches!(*mutex_guard, InitState::Initialized(_)));
158-
114+
/// The value must be initialized.
115+
pub(super) unsafe fn new_unchecked(mutex_guard: MutexGuard<'a, OnceLock<T>>) -> Self {
116+
debug_assert!(mutex_guard.get().is_some());
159117
Self::new(mutex_guard).unwrap_unchecked()
160118
}
161119
}
162120

163-
impl<T> Deref for GlobalGuard<'_, T> {
121+
impl<'a, T> Deref for GlobalGuard<'a, T> {
164122
type Target = T;
165-
166123
fn deref(&self) -> &Self::Target {
167-
// SAFETY: `self` is `Initialized`.
168-
unsafe { self.mutex_guard.as_initialized().unwrap_unchecked() }
124+
// SAFETY: must have been init (see GlobalGuard::new)
125+
unsafe { self.mutex_guard.get().unwrap_unchecked() }
169126
}
170127
}
171128

172129
impl<T> DerefMut for GlobalGuard<'_, T> {
173130
fn deref_mut(&mut self) -> &mut Self::Target {
174-
// SAFETY: `self` is `Initialized`.
175-
unsafe { self.mutex_guard.as_initialized_mut().unwrap_unchecked() }
131+
unsafe { self.mutex_guard.get_mut().unwrap_unchecked() }
176132
}
177133
}
178134
}
179135

180136
pub use global_guard::GlobalGuard;
181137

182-
// ----------------------------------------------------------------------------------------------------------------------------------------------
183-
// Errors
184-
185138
/// Guard that temporarily gives access to a `Global<T>`'s inner value.
186139
pub enum GlobalLockError<'a, T> {
187140
/// The mutex is currently locked by another thread.
@@ -194,31 +147,6 @@ pub enum GlobalLockError<'a, T> {
194147
InitFailed,
195148
}
196149

197-
// ----------------------------------------------------------------------------------------------------------------------------------------------
198-
// Internals
199-
200-
enum InitState<T> {
201-
Initialized(T),
202-
Pending(fn() -> T),
203-
Failed,
204-
}
205-
206-
impl<T> InitState<T> {
207-
fn as_initialized(&self) -> Option<&T> {
208-
match self {
209-
InitState::Initialized(t) => Some(t),
210-
_ => None,
211-
}
212-
}
213-
214-
fn as_initialized_mut(&mut self) -> Option<&mut T> {
215-
match self {
216-
InitState::Initialized(t) => Some(t),
217-
_ => None,
218-
}
219-
}
220-
}
221-
222150
// ----------------------------------------------------------------------------------------------------------------------------------------------
223151
// Tests
224152

@@ -283,6 +211,7 @@ mod tests {
283211

284212
#[test]
285213
fn test_global_poison() {
214+
std::panic::set_hook(Box::new(|_| ()));
286215
let result = std::panic::catch_unwind(|| {
287216
let guard = POISON.lock();
288217
panic!("poison injection");

0 commit comments

Comments
 (0)