@@ -92,6 +92,23 @@ pub mod imp;
92
92
#[ path = "gcc.rs" ] #[ doc( hidden) ]
93
93
pub mod imp;
94
94
95
+ pub type Callback = fn ( msg : & ( Any + Send ) , file : & ' static str , line : u32 ) ;
96
+
97
+ // Variables used for invoking callbacks when a thread starts to unwind.
98
+ //
99
+ // For more information, see below.
100
+ const MAX_CALLBACKS : usize = 16 ;
101
+ static CALLBACKS : [ atomic:: AtomicUsize ; MAX_CALLBACKS ] =
102
+ [ atomic:: AtomicUsize :: new ( 0 ) , atomic:: AtomicUsize :: new ( 0 ) ,
103
+ atomic:: AtomicUsize :: new ( 0 ) , atomic:: AtomicUsize :: new ( 0 ) ,
104
+ atomic:: AtomicUsize :: new ( 0 ) , atomic:: AtomicUsize :: new ( 0 ) ,
105
+ atomic:: AtomicUsize :: new ( 0 ) , atomic:: AtomicUsize :: new ( 0 ) ,
106
+ atomic:: AtomicUsize :: new ( 0 ) , atomic:: AtomicUsize :: new ( 0 ) ,
107
+ atomic:: AtomicUsize :: new ( 0 ) , atomic:: AtomicUsize :: new ( 0 ) ,
108
+ atomic:: AtomicUsize :: new ( 0 ) , atomic:: AtomicUsize :: new ( 0 ) ,
109
+ atomic:: AtomicUsize :: new ( 0 ) , atomic:: AtomicUsize :: new ( 0 ) ] ;
110
+ static CALLBACK_CNT : atomic:: AtomicUsize = atomic:: AtomicUsize :: new ( 0 ) ;
111
+
95
112
thread_local ! { static PANICKING : Cell <bool > = Cell :: new( false ) }
96
113
97
114
/// Invoke a closure, capturing the cause of panic if one occurs.
@@ -229,11 +246,45 @@ pub fn begin_unwind<M: Any + Send>(msg: M, file_line: &(&'static str, u32)) -> !
229
246
#[ inline( never) ] #[ cold] // this is the slow path, please never inline this
230
247
fn begin_unwind_inner ( msg : Box < Any + Send > ,
231
248
file_line : & ( & ' static str , u32 ) ) -> ! {
232
- let ( file, line) = * file_line;
249
+ // Make sure the default failure handler is registered before we look at the
250
+ // callbacks. We also use a raw sys-based mutex here instead of a
251
+ // `std::sync` one as accessing TLS can cause weird recursive problems (and
252
+ // we don't need poison checking).
253
+ unsafe {
254
+ static LOCK : Mutex = Mutex :: new ( ) ;
255
+ static mut INIT : bool = false ;
256
+ LOCK . lock ( ) ;
257
+ if !INIT {
258
+ register ( panicking:: on_panic) ;
259
+ INIT = true ;
260
+ }
261
+ LOCK . unlock ( ) ;
262
+ }
233
263
234
- // First, invoke the default panic handler.
235
- panicking:: on_panic ( & * msg, file, line) ;
264
+ // First, invoke call the user-defined callbacks triggered on thread panic.
265
+ //
266
+ // By the time that we see a callback has been registered (by reading
267
+ // MAX_CALLBACKS), the actual callback itself may have not been stored yet,
268
+ // so we just chalk it up to a race condition and move on to the next
269
+ // callback. Additionally, CALLBACK_CNT may briefly be higher than
270
+ // MAX_CALLBACKS, so we're sure to clamp it as necessary.
271
+ let callbacks = {
272
+ let amt = CALLBACK_CNT . load ( Ordering :: SeqCst ) ;
273
+ & CALLBACKS [ ..cmp:: min ( amt, MAX_CALLBACKS ) ]
274
+ } ;
275
+ for cb in callbacks {
276
+ match cb. load ( Ordering :: SeqCst ) {
277
+ 0 => { }
278
+ n => {
279
+ let f: Callback = unsafe { mem:: transmute ( n) } ;
280
+ let ( file, line) = * file_line;
281
+ f ( & * msg, file, line) ;
282
+ }
283
+ }
284
+ } ;
236
285
286
+ // Now that we've run all the necessary unwind callbacks, we actually
287
+ // perform the unwinding.
237
288
if panicking ( ) {
238
289
// If a thread panics while it's already unwinding then we
239
290
// have limited options. Currently our preference is to
@@ -244,7 +295,34 @@ fn begin_unwind_inner(msg: Box<Any + Send>,
244
295
unsafe { intrinsics:: abort ( ) }
245
296
}
246
297
PANICKING . with ( |s| s. set ( true ) ) ;
247
-
248
- // Finally, perform the unwinding.
249
298
rust_panic ( msg) ;
250
299
}
300
+
301
+ /// Register a callback to be invoked when a thread unwinds.
302
+ ///
303
+ /// This is an unsafe and experimental API which allows for an arbitrary
304
+ /// callback to be invoked when a thread panics. This callback is invoked on both
305
+ /// the initial unwinding and a double unwinding if one occurs. Additionally,
306
+ /// the local `Thread` will be in place for the duration of the callback, and
307
+ /// the callback must ensure that it remains in place once the callback returns.
308
+ ///
309
+ /// Only a limited number of callbacks can be registered, and this function
310
+ /// returns whether the callback was successfully registered or not. It is not
311
+ /// currently possible to unregister a callback once it has been registered.
312
+ pub unsafe fn register ( f : Callback ) -> bool {
313
+ match CALLBACK_CNT . fetch_add ( 1 , Ordering :: SeqCst ) {
314
+ // The invocation code has knowledge of this window where the count has
315
+ // been incremented, but the callback has not been stored. We're
316
+ // guaranteed that the slot we're storing into is 0.
317
+ n if n < MAX_CALLBACKS => {
318
+ let prev = CALLBACKS [ n] . swap ( mem:: transmute ( f) , Ordering :: SeqCst ) ;
319
+ rtassert ! ( prev == 0 ) ;
320
+ true
321
+ }
322
+ // If we accidentally bumped the count too high, pull it back.
323
+ _ => {
324
+ CALLBACK_CNT . store ( MAX_CALLBACKS , Ordering :: SeqCst ) ;
325
+ false
326
+ }
327
+ }
328
+ }
0 commit comments