@@ -160,32 +160,95 @@ mod lock {
160
160
use std:: ptr;
161
161
use std:: sync:: { Mutex , MutexGuard , Once } ;
162
162
163
+ /// A "Maybe" LockGuard
163
164
pub struct LockGuard ( Option < MutexGuard < ' static , ( ) > > ) ;
164
165
166
+ /// The global lock, lazily allocated on first use
165
167
static mut LOCK : * mut Mutex < ( ) > = ptr:: null_mut ( ) ;
166
168
static INIT : Once = Once :: new ( ) ;
169
+ // Whether this thread is the one that holds the lock
167
170
thread_local ! ( static LOCK_HELD : Cell <bool > = Cell :: new( false ) ) ;
168
171
169
172
impl Drop for LockGuard {
170
173
fn drop ( & mut self ) {
174
+ // Don't do anything if we're a LockGuard(None)
171
175
if self . 0 . is_some ( ) {
172
176
LOCK_HELD . with ( |slot| {
177
+ // Immediately crash if we somehow aren't the thread holding this lock
173
178
assert ! ( slot. get( ) ) ;
179
+ // We are no longer the thread holding this lock
174
180
slot. set ( false ) ;
175
181
} ) ;
176
182
}
183
+ // lock implicitly released here, if we're a LockGuard(Some(..))
177
184
}
178
185
}
179
186
187
+ /// Acquire a partially unsound(!!!) global re-entrant lock over
188
+ /// backtrace's internals.
189
+ ///
190
+ /// That is, this lock can be acquired as many times as you want
191
+ /// on a single thread without deadlocking, allowing one thread
192
+ /// to acquire exclusive access to the ability to make backtraces.
193
+ /// Calls to this locking function are freely sprinkled in every place
194
+ /// where that needs to be enforced.
195
+ ///
196
+ ///
197
+ /// # Why
198
+ ///
199
+ /// This was first introduced to guard uses of Windows' dbghelp API,
200
+ /// which isn't threadsafe. It's unclear if other things now rely on
201
+ /// this locking.
202
+ ///
203
+ ///
204
+ /// # How
205
+ ///
206
+ /// The basic idea is to have a single global mutex, and a thread_local
207
+ /// boolean saying "yep this is the thread that acquired the mutex".
208
+ ///
209
+ /// The first time a thread acquires the lock, it is handed a
210
+ /// `LockGuard(Some(..))` that will actually release the lock on Drop.
211
+ /// All subsequence attempts to lock on the same thread will see
212
+ /// that their thread acquired the lock, and get `LockGuard(None)`
213
+ /// which will do nothing when dropped.
214
+ ///
215
+ ///
216
+ /// # Safety
217
+ ///
218
+ /// As long as you only ever assign the returned LockGuard to a freshly
219
+ /// declared local variable, it will do its job correctly, as the "first"
220
+ /// LockGuard will strictly outlive all subsequent LockGuards and
221
+ /// properly release the lock when the thread is done with backtracing.
222
+ ///
223
+ /// However if you ever attempt to store a LockGuard beyond the scope
224
+ /// it was acquired in, it might actually be a `LockGuard(None)` that
225
+ /// doesn't actually hold the lock! In this case another thread might
226
+ /// acquire the lock and you'll get races this system was intended to
227
+ /// avoid!
228
+ ///
229
+ /// This is why this is "partially unsound". As a public API this would
230
+ /// be unacceptable, but this is crate-private, and if you use this in
231
+ /// the most obvious and simplistic way it Just Works™.
232
+ ///
233
+ /// Note however that std specifically bypasses this lock, and uses
234
+ /// the `*_unsynchronized` backtrace APIs. This is "fine" because
235
+ /// it wraps its own calls to backtrace in a non-reentrant Mutex
236
+ /// that prevents two backtraces from getting interleaved during printing.
180
237
pub fn lock ( ) -> LockGuard {
238
+ // If we're the thread holding this lock, pretend to acquire the lock
239
+ // again by returning a LockGuard(None)
181
240
if LOCK_HELD . with ( |l| l. get ( ) ) {
182
241
return LockGuard ( None ) ;
183
242
}
243
+ // Insist that we totally are the thread holding the lock
244
+ // (our thread will block until we are)
184
245
LOCK_HELD . with ( |s| s. set ( true ) ) ;
185
246
unsafe {
247
+ // lazily allocate the lock if necessary
186
248
INIT . call_once ( || {
187
249
LOCK = Box :: into_raw ( Box :: new ( Mutex :: new ( ( ) ) ) ) ;
188
250
} ) ;
251
+ // ok *actually* try to acquire the lock, blocking as necessary
189
252
LockGuard ( Some ( ( * LOCK ) . lock ( ) . unwrap ( ) ) )
190
253
}
191
254
}
0 commit comments