6
6
*/
7
7
8
8
use std:: ops:: { Deref , DerefMut } ;
9
- use std:: sync:: { Mutex , MutexGuard } ;
9
+ use std:: sync:: { Mutex , MutexGuard , TryLockError } ;
10
10
11
11
/// Ergonomic global variables.
12
12
///
@@ -19,8 +19,8 @@ use std::sync::{Mutex, MutexGuard};
19
19
/// - Ergonomic access through guards to both `&T` and `&mut T`.
20
20
/// - Completely safe usage. Almost completely safe implementation (besides `unreachable_unchecked`).
21
21
///
22
- /// There are two main methods: [`new()`](Self::new) and [`lock()`](Self::lock). Additionally, [` default()`](Self::default) is provided
23
- /// for convenience .
22
+ /// There are two methods for construction : [`new()`](Self::new) and [`default()`](Self::default).
23
+ /// For access, you should primarily use [`lock()`](Self::lock). There is also [`try_lock()`](Self::try_lock) for special cases .
24
24
pub struct Global < T > {
25
25
// When needed, this could be changed to use RwLock and separate read/write guards.
26
26
value : Mutex < InitState < T > > ,
@@ -55,24 +55,57 @@ impl<T> Global<T> {
55
55
/// If the initialization function panics. Once that happens, the global is considered poisoned and all future calls to `lock()` will panic.
56
56
/// This can currently not be recovered from.
57
57
pub fn lock ( & self ) -> GlobalGuard < ' _ , T > {
58
- let guard = self . ensure_init ( ) ;
59
- debug_assert ! ( matches!( * guard, InitState :: Initialized ( _) ) ) ;
58
+ let mutex_guard = self
59
+ . value
60
+ . lock ( )
61
+ . expect ( "Global<T> poisoned; a thread has panicked while holding a lock to it" ) ;
60
62
61
- GlobalGuard { guard }
63
+ let guard = Self :: ensure_init ( mutex_guard, true )
64
+ . unwrap_or_else ( || panic ! ( "previous Global<T> initialization failed due to panic" ) ) ;
65
+
66
+ debug_assert ! ( matches!( * guard. mutex_guard, InitState :: Initialized ( _) ) ) ;
67
+ guard
62
68
}
63
69
64
- fn ensure_init ( & self ) -> MutexGuard < ' _ , InitState < T > > {
65
- let mut guard = self . value . lock ( ) . expect ( "lock poisoned" ) ;
66
- let pending_state = match & mut * guard {
70
+ /// Non-panicking access with error introspection.
71
+ pub fn try_lock ( & self ) -> Result < GlobalGuard < ' _ , T > , GlobalLockError < ' _ , T > > {
72
+ let guard = match self . value . try_lock ( ) {
73
+ Ok ( mutex_guard) => Self :: ensure_init ( mutex_guard, false ) ,
74
+ Err ( TryLockError :: WouldBlock ) => {
75
+ return Err ( GlobalLockError :: WouldBlock ) ;
76
+ }
77
+ Err ( TryLockError :: Poisoned ( poisoned) ) => {
78
+ return Err ( GlobalLockError :: Poisoned {
79
+ circumvent : GlobalGuard {
80
+ mutex_guard : poisoned. into_inner ( ) ,
81
+ } ,
82
+ } ) ;
83
+ }
84
+ } ;
85
+
86
+ match guard {
87
+ None => Err ( GlobalLockError :: InitFailed ) ,
88
+ Some ( guard) => {
89
+ debug_assert ! ( matches!( * guard. mutex_guard, InitState :: Initialized ( _) ) ) ;
90
+ Ok ( guard)
91
+ }
92
+ }
93
+ }
94
+
95
+ fn ensure_init (
96
+ mut mutex_guard : MutexGuard < ' _ , InitState < T > > ,
97
+ may_panic : bool ,
98
+ ) -> Option < GlobalGuard < ' _ , T > > {
99
+ let pending_state = match & mut * mutex_guard {
67
100
InitState :: Initialized ( _) => {
68
- return guard ;
101
+ return Some ( GlobalGuard { mutex_guard } ) ;
69
102
}
70
103
InitState :: TransientInitializing => {
71
104
// SAFETY: only set inside this function and all paths (panic + return) leave the enum in a different state.
72
105
unsafe { std:: hint:: unreachable_unchecked ( ) } ;
73
106
}
74
107
InitState :: Failed => {
75
- panic ! ( "previous Global<T> initialization failed due to panic" )
108
+ return None ;
76
109
}
77
110
state @ InitState :: Pending ( _) => {
78
111
std:: mem:: replace ( state, InitState :: TransientInitializing )
@@ -87,15 +120,21 @@ impl<T> Global<T> {
87
120
// Unwinding should be safe here, as there is no unsafe code relying on it.
88
121
let init_fn = std:: panic:: AssertUnwindSafe ( init_fn) ;
89
122
match std:: panic:: catch_unwind ( init_fn) {
90
- Ok ( value) => * guard = InitState :: Initialized ( value) ,
123
+ Ok ( value) => * mutex_guard = InitState :: Initialized ( value) ,
91
124
Err ( e) => {
92
125
eprintln ! ( "panic during Global<T> initialization" ) ;
93
- * guard = InitState :: Failed ;
94
- std:: panic:: resume_unwind ( e) ;
126
+ * mutex_guard = InitState :: Failed ;
127
+
128
+ if may_panic {
129
+ std:: panic:: resume_unwind ( e) ;
130
+ } else {
131
+ // Note: this currently swallows panic.
132
+ return None ;
133
+ }
95
134
}
96
135
} ;
97
136
98
- guard
137
+ Some ( GlobalGuard { mutex_guard } )
99
138
}
100
139
}
101
140
@@ -104,23 +143,38 @@ impl<T> Global<T> {
104
143
105
144
/// Guard that temporarily gives access to a `Global<T>`'s inner value.
106
145
pub struct GlobalGuard < ' a , T > {
107
- guard : MutexGuard < ' a , InitState < T > > ,
146
+ mutex_guard : MutexGuard < ' a , InitState < T > > ,
108
147
}
109
148
110
149
impl < T > Deref for GlobalGuard < ' _ , T > {
111
150
type Target = T ;
112
151
113
152
fn deref ( & self ) -> & Self :: Target {
114
- self . guard . unwrap_ref ( )
153
+ self . mutex_guard . unwrap_ref ( )
115
154
}
116
155
}
117
156
118
157
impl < T > DerefMut for GlobalGuard < ' _ , T > {
119
158
fn deref_mut ( & mut self ) -> & mut Self :: Target {
120
- self . guard . unwrap_mut ( )
159
+ self . mutex_guard . unwrap_mut ( )
121
160
}
122
161
}
123
162
163
+ // ----------------------------------------------------------------------------------------------------------------------------------------------
164
+ // Errors
165
+
166
+ /// Guard that temporarily gives access to a `Global<T>`'s inner value.
167
+ pub enum GlobalLockError < ' a , T > {
168
+ /// The mutex is currently locked by another thread.
169
+ WouldBlock ,
170
+
171
+ /// A panic occurred while a lock was held. This excludes initialization errors.
172
+ Poisoned { circumvent : GlobalGuard < ' a , T > } ,
173
+
174
+ /// The initialization function panicked.
175
+ InitFailed ,
176
+ }
177
+
124
178
// ----------------------------------------------------------------------------------------------------------------------------------------------
125
179
// Internals
126
180
@@ -163,6 +217,8 @@ mod tests {
163
217
164
218
static MAP : Global < HashMap < i32 , & ' static str > > = Global :: default ( ) ;
165
219
static VEC : Global < Vec < i32 > > = Global :: new ( || vec ! [ 1 , 2 , 3 ] ) ;
220
+ static FAILED : Global < ( ) > = Global :: new ( || panic ! ( "failed" ) ) ;
221
+ static POISON : Global < i32 > = Global :: new ( || 36 ) ;
166
222
167
223
#[ test]
168
224
fn test_global_map ( ) {
@@ -194,4 +250,37 @@ mod tests {
194
250
let vec = VEC . lock ( ) ;
195
251
assert_eq ! ( * vec, & [ 1 , 2 , 3 , 4 ] ) ;
196
252
}
253
+
254
+ #[ test]
255
+ fn test_global_would_block ( ) {
256
+ let vec = VEC . lock ( ) ;
257
+
258
+ let vec2 = VEC . try_lock ( ) ;
259
+ assert ! ( matches!( vec2, Err ( GlobalLockError :: WouldBlock ) ) ) ;
260
+ }
261
+
262
+ #[ test]
263
+ fn test_global_init_failed ( ) {
264
+ let guard = FAILED . try_lock ( ) ;
265
+ assert ! ( matches!( guard, Err ( GlobalLockError :: InitFailed ) ) ) ;
266
+
267
+ // Subsequent access still returns same error.
268
+ let guard = FAILED . try_lock ( ) ;
269
+ assert ! ( matches!( guard, Err ( GlobalLockError :: InitFailed ) ) ) ;
270
+ }
271
+
272
+ #[ test]
273
+ fn test_global_poison ( ) {
274
+ let result = std:: panic:: catch_unwind ( || {
275
+ let guard = POISON . lock ( ) ;
276
+ panic ! ( "poison injection" ) ;
277
+ } ) ;
278
+ assert ! ( result. is_err( ) ) ;
279
+
280
+ let guard = POISON . try_lock ( ) ;
281
+ let Err ( GlobalLockError :: Poisoned { circumvent } ) = guard else {
282
+ panic ! ( "expected GlobalLockError::Poisoned" ) ;
283
+ } ;
284
+ assert_eq ! ( * circumvent, 36 ) ;
285
+ }
197
286
}
0 commit comments