Skip to content

Commit d949495

Browse files
committed
Global: changed to wrap Mutex<OnceLock>
1 parent 79edae3 commit d949495

File tree

1 file changed

+52
-107
lines changed

1 file changed

+52
-107
lines changed

godot-ffi/src/global.rs

Lines changed: 52 additions & 107 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::cell::OnceCell;
9+
use std::sync::{Mutex, MutexGuard, PoisonError, TryLockError};
910

1011
/// Ergonomic global variables.
1112
///
@@ -25,7 +26,8 @@ use std::sync::{Mutex, MutexGuard, TryLockError};
2526
/// For access, you should primarily use [`lock()`](Self::lock). There is also [`try_lock()`](Self::try_lock) for special cases.
2627
pub struct Global<T> {
2728
// When needed, this could be changed to use RwLock and separate read/write guards.
28-
value: Mutex<InitState<T>>,
29+
value: Mutex<OnceCell<T>>,
30+
init_fn: fn() -> T,
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(OnceCell::new()),
41+
init_fn,
3942
}
4043
}
4144

@@ -57,72 +60,41 @@ 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");
63+
let guard = self.value.lock().unwrap_or_else(PoisonError::into_inner);
64+
guard.get_or_init(self.init_fn);
6465

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
66+
// SAFETY: `get_or_init()` has already panicked if it wants to, propogating the panic to its caller,
67+
// so the object is guaranteed to be initialized.
68+
unsafe { GlobalGuard::new_unchecked(guard) }
6969
}
7070

71-
/// Non-panicking access with error introspection.
71+
/// Non-blocking access with error introspection.
7272
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-
};
73+
/// Initializes the cell and returns a guard.
74+
fn init<'mutex: 'cell, 'cell, T>(
75+
g: MutexGuard<'mutex, OnceCell<T>>,
76+
init_fn: fn() -> T,
77+
) -> Result<GlobalGuard<'cell, T>, GlobalLockError<'cell, T>> {
78+
// Initialize the cell.
79+
std::panic::catch_unwind(|| g.get_or_init(init_fn))
80+
.map_err(|_| GlobalLockError::InitFailed)?;
81+
82+
// SAFETY: `get_or_init()` has already panicked if it wants to, which has been successfully unwound,
83+
// therefore the object is guaranteed to be initialized.
84+
Ok(unsafe { GlobalGuard::new_unchecked(g) })
85+
}
8886

89-
guard.ok_or(GlobalLockError::InitFailed)
90-
}
87+
match self.value.try_lock() {
88+
Ok(guard) => init(guard, self.init_fn),
89+
Err(TryLockError::WouldBlock) => Err(GlobalLockError::WouldBlock),
9190

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) });
91+
// This is a cold branch, where the initialization function panicked.
92+
Err(TryLockError::Poisoned(x)) => {
93+
// We do the same things as in the hot branch.
94+
let circumvent = init(x.into_inner(), self.init_fn)?;
95+
Err(GlobalLockError::Poisoned { circumvent })
10096
}
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;
120-
}
121-
}
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) })
97+
}
12698
}
12799
}
128100

@@ -131,57 +103,55 @@ impl<T> Global<T> {
131103

132104
// Encapsulate private fields.
133105
mod global_guard {
106+
use super::*;
134107
use std::ops::{Deref, DerefMut};
135-
use std::sync::MutexGuard;
136-
137-
use super::InitState;
138108

139109
/// Guard that temporarily gives access to a `Global<T>`'s inner value.
140110
pub struct GlobalGuard<'a, T> {
141-
// Safety invariant: Is `Initialized`.
142-
mutex_guard: MutexGuard<'a, InitState<T>>,
111+
// Safety invariant: `OnceCell` has been initialized.
112+
mutex_guard: MutexGuard<'a, OnceCell<T>>,
143113
}
144114

145115
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 }),
116+
pub(super) fn new(mutex_guard: MutexGuard<'a, OnceCell<T>>) -> Option<Self> {
117+
// Use an eager map instead of `mutex_guard.get().map(|_| Self { mutex_guard })`
118+
// as `.get().map(…)` trys to move `mutex_guard` while borrowing an ignored value.
119+
match mutex_guard.get() {
120+
Some(_) => Some(Self { mutex_guard }),
149121
_ => None,
150122
}
151123
}
152124

153125
/// # Safety
154126
///
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-
127+
/// The value must be initialized.
128+
pub(super) unsafe fn new_unchecked(mutex_guard: MutexGuard<'a, OnceCell<T>>) -> Self {
129+
debug_assert!(
130+
mutex_guard.get().is_some(),
131+
"safety precondition violated: cell not initialized"
132+
);
159133
Self::new(mutex_guard).unwrap_unchecked()
160134
}
161135
}
162136

163137
impl<T> Deref for GlobalGuard<'_, T> {
164138
type Target = T;
165-
166139
fn deref(&self) -> &Self::Target {
167-
// SAFETY: `self` is `Initialized`.
168-
unsafe { self.mutex_guard.as_initialized().unwrap_unchecked() }
140+
// SAFETY: `GlobalGuard` guarantees that the cell is initialized.
141+
unsafe { self.mutex_guard.get().unwrap_unchecked() }
169142
}
170143
}
171144

172145
impl<T> DerefMut for GlobalGuard<'_, T> {
173146
fn deref_mut(&mut self) -> &mut Self::Target {
174-
// SAFETY: `self` is `Initialized`.
175-
unsafe { self.mutex_guard.as_initialized_mut().unwrap_unchecked() }
147+
// SAFETY: `GlobalGuard` guarantees that the cell is initialized.
148+
unsafe { self.mutex_guard.get_mut().unwrap_unchecked() }
176149
}
177150
}
178151
}
179152

180153
pub use global_guard::GlobalGuard;
181154

182-
// ----------------------------------------------------------------------------------------------------------------------------------------------
183-
// Errors
184-
185155
/// Guard that temporarily gives access to a `Global<T>`'s inner value.
186156
pub enum GlobalLockError<'a, T> {
187157
/// The mutex is currently locked by another thread.
@@ -194,31 +164,6 @@ pub enum GlobalLockError<'a, T> {
194164
InitFailed,
195165
}
196166

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-
222167
// ----------------------------------------------------------------------------------------------------------------------------------------------
223168
// Tests
224169

0 commit comments

Comments
 (0)