Skip to content

Commit 7871677

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

File tree

1 file changed

+36
-135
lines changed

1 file changed

+36
-135
lines changed

godot-ffi/src/global.rs

Lines changed: 36 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
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::ops::{Deref, DerefMut};
9+
use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError, TryLockError};
910

1011
/// Ergonomic global variables.
1112
///
@@ -23,9 +24,10 @@ use std::sync::{Mutex, MutexGuard, TryLockError};
2324
///
2425
/// There are two `const` methods for construction: [`new()`](Self::new) and [`default()`](Self::default).
2526
/// 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> {
27+
pub struct Global<T, F = fn() -> T> {
2728
// When needed, this could be changed to use RwLock and separate read/write guards.
28-
value: Mutex<InitState<T>>,
29+
value: Mutex<OnceLock<T>>,
30+
init_fn: F,
2931
}
3032

3133
impl<T> Global<T> {
@@ -35,7 +37,8 @@ impl<T> Global<T> {
3537
pub const fn new(init_fn: fn() -> T) -> Self {
3638
// Note: could be generalized to F: FnOnce() -> T + Send. See also once_cell::Lazy<T, F>.
3739
Self {
38-
value: Mutex::new(InitState::Pending(init_fn)),
40+
value: Mutex::new(OnceLock::new()),
41+
init_fn,
3942
}
4043
}
4144

@@ -57,131 +60,53 @@ impl<T> Global<T> {
5760
/// If the initialization function panics. Once that happens, the global is considered poisoned and all future calls to `lock()` will panic.
5861
/// This can currently not be recovered from.
5962
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
63+
let guard = self.value.lock().unwrap_or_else(PoisonError::into_inner);
64+
guard.get_or_init(self.init_fn);
65+
GlobalGuard { mutex_guard: 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 mutex_guard = x.into_inner();
73+
match std::panic::catch_unwind(|| mutex_guard.get_or_init(self.init_fn)) {
74+
Ok(_) => GlobalLockError::Poisoned {
75+
circumvent: GlobalGuard { mutex_guard },
76+
},
77+
Err(_) => GlobalLockError::InitFailed,
12078
}
12179
}
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) })
80+
TryLockError::WouldBlock => GlobalLockError::WouldBlock,
81+
})?;
82+
std::panic::catch_unwind(|| guard.get_or_init(self.init_fn))
83+
.map_err(|_| GlobalLockError::InitFailed)?;
84+
Ok(GlobalGuard { mutex_guard: guard })
12685
}
12786
}
12887

12988
// ----------------------------------------------------------------------------------------------------------------------------------------------
13089
// Guards
13190

132-
// Encapsulate private fields.
133-
mod global_guard {
134-
use std::ops::{Deref, DerefMut};
135-
use std::sync::MutexGuard;
91+
/// Guard that temporarily gives access to a `Global<T>`'s inner value.
92+
pub struct GlobalGuard<'a, T> {
93+
mutex_guard: MutexGuard<'a, OnceLock<T>>,
94+
}
13695

13796
use super::InitState;
13897

139-
/// Guard that temporarily gives access to a `Global<T>`'s inner value.
140-
pub struct GlobalGuard<'a, T> {
141-
// Safety invariant: Is `Initialized`.
142-
mutex_guard: MutexGuard<'a, InitState<T>>,
143-
}
144-
145-
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 }),
149-
_ => None,
150-
}
151-
}
152-
153-
/// # Safety
154-
///
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-
159-
Self::new(mutex_guard).unwrap_unchecked()
160-
}
161-
}
162-
163-
impl<T> Deref for GlobalGuard<'_, T> {
164-
type Target = T;
165-
166-
fn deref(&self) -> &Self::Target {
167-
// SAFETY: `self` is `Initialized`.
168-
unsafe { self.mutex_guard.as_initialized().unwrap_unchecked() }
169-
}
98+
fn deref(&self) -> &Self::Target {
99+
// SAFETY: must have been init
100+
unsafe { self.mutex_guard.get().unwrap_unchecked() }
170101
}
102+
}
171103

172-
impl<T> DerefMut for GlobalGuard<'_, T> {
173-
fn deref_mut(&mut self) -> &mut Self::Target {
174-
// SAFETY: `self` is `Initialized`.
175-
unsafe { self.mutex_guard.as_initialized_mut().unwrap_unchecked() }
176-
}
104+
impl<T> DerefMut for GlobalGuard<'_, T> {
105+
fn deref_mut(&mut self) -> &mut Self::Target {
106+
unsafe { self.mutex_guard.get_mut().unwrap_unchecked() }
177107
}
178108
}
179109

180-
pub use global_guard::GlobalGuard;
181-
182-
// ----------------------------------------------------------------------------------------------------------------------------------------------
183-
// Errors
184-
185110
/// Guard that temporarily gives access to a `Global<T>`'s inner value.
186111
pub enum GlobalLockError<'a, T> {
187112
/// The mutex is currently locked by another thread.
@@ -194,31 +119,6 @@ pub enum GlobalLockError<'a, T> {
194119
InitFailed,
195120
}
196121

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-
222122
// ----------------------------------------------------------------------------------------------------------------------------------------------
223123
// Tests
224124

@@ -283,6 +183,7 @@ mod tests {
283183

284184
#[test]
285185
fn test_global_poison() {
186+
std::panic::set_hook(Box::new(|_| ()));
286187
let result = std::panic::catch_unwind(|| {
287188
let guard = POISON.lock();
288189
panic!("poison injection");

0 commit comments

Comments
 (0)