Skip to content

Commit c55ccc4

Browse files
committed
Auto merge of #110975 - Amanieu:panic_count, r=joshtriplett
Rework handling of recursive panics This PR makes 2 changes to how recursive panics works (a panic while handling a panic). 1. The panic count is no longer used to determine whether to force an immediate abort. This allows code like the following to work without aborting the process immediately: ```rust struct Double; impl Drop for Double { fn drop(&mut self) { // 2 panics are active at once, but this is fine since it is caught. std::panic::catch_unwind(|| panic!("twice")); } } let _d = Double; panic!("once"); ``` Rustc already generates appropriate code so that any exceptions escaping out of a `Drop` called in the unwind path will immediately abort the process. 2. Any panics while the panic hook is executing will force an immediate abort. This is necessary to avoid potential deadlocks like #110771 where a panic happens while holding the backtrace lock. We don't even try to print the panic message in this case since the panic may have been caused by `Display` impls. Fixes #110771
2 parents 7255a90 + 8f370bd commit c55ccc4

File tree

1 file changed

+67
-53
lines changed

1 file changed

+67
-53
lines changed

std/src/panicking.rs

Lines changed: 67 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,18 @@ pub mod panic_count {
298298

299299
pub const ALWAYS_ABORT_FLAG: usize = 1 << (usize::BITS - 1);
300300

301-
// Panic count for the current thread.
302-
thread_local! { static LOCAL_PANIC_COUNT: Cell<usize> = const { Cell::new(0) } }
301+
/// A reason for forcing an immediate abort on panic.
302+
#[derive(Debug)]
303+
pub enum MustAbort {
304+
AlwaysAbort,
305+
PanicInHook,
306+
}
307+
308+
// Panic count for the current thread and whether a panic hook is currently
309+
// being executed..
310+
thread_local! {
311+
static LOCAL_PANIC_COUNT: Cell<(usize, bool)> = const { Cell::new((0, false)) }
312+
}
303313

304314
// Sum of panic counts from all threads. The purpose of this is to have
305315
// a fast path in `count_is_zero` (which is used by `panicking`). In any particular
@@ -328,34 +338,39 @@ pub mod panic_count {
328338
// panicking thread consumes at least 2 bytes of address space.
329339
static GLOBAL_PANIC_COUNT: AtomicUsize = AtomicUsize::new(0);
330340

331-
// Return the state of the ALWAYS_ABORT_FLAG and number of panics.
341+
// Increases the global and local panic count, and returns whether an
342+
// immediate abort is required.
332343
//
333-
// If ALWAYS_ABORT_FLAG is not set, the number is determined on a per-thread
334-
// base (stored in LOCAL_PANIC_COUNT), i.e. it is the amount of recursive calls
335-
// of the calling thread.
336-
// If ALWAYS_ABORT_FLAG is set, the number equals the *global* number of panic
337-
// calls. See above why LOCAL_PANIC_COUNT is not used.
338-
pub fn increase() -> (bool, usize) {
344+
// This also updates thread-local state to keep track of whether a panic
345+
// hook is currently executing.
346+
pub fn increase(run_panic_hook: bool) -> Option<MustAbort> {
339347
let global_count = GLOBAL_PANIC_COUNT.fetch_add(1, Ordering::Relaxed);
340-
let must_abort = global_count & ALWAYS_ABORT_FLAG != 0;
341-
let panics = if must_abort {
342-
global_count & !ALWAYS_ABORT_FLAG
343-
} else {
344-
LOCAL_PANIC_COUNT.with(|c| {
345-
let next = c.get() + 1;
346-
c.set(next);
347-
next
348-
})
349-
};
350-
(must_abort, panics)
348+
if global_count & ALWAYS_ABORT_FLAG != 0 {
349+
return Some(MustAbort::AlwaysAbort);
350+
}
351+
352+
LOCAL_PANIC_COUNT.with(|c| {
353+
let (count, in_panic_hook) = c.get();
354+
if in_panic_hook {
355+
return Some(MustAbort::PanicInHook);
356+
}
357+
c.set((count + 1, run_panic_hook));
358+
None
359+
})
360+
}
361+
362+
pub fn finished_panic_hook() {
363+
LOCAL_PANIC_COUNT.with(|c| {
364+
let (count, _) = c.get();
365+
c.set((count, false));
366+
});
351367
}
352368

353369
pub fn decrease() {
354370
GLOBAL_PANIC_COUNT.fetch_sub(1, Ordering::Relaxed);
355371
LOCAL_PANIC_COUNT.with(|c| {
356-
let next = c.get() - 1;
357-
c.set(next);
358-
next
372+
let (count, _) = c.get();
373+
c.set((count - 1, false));
359374
});
360375
}
361376

@@ -366,7 +381,7 @@ pub mod panic_count {
366381
// Disregards ALWAYS_ABORT_FLAG
367382
#[must_use]
368383
pub fn get_count() -> usize {
369-
LOCAL_PANIC_COUNT.with(|c| c.get())
384+
LOCAL_PANIC_COUNT.with(|c| c.get().0)
370385
}
371386

372387
// Disregards ALWAYS_ABORT_FLAG
@@ -394,7 +409,7 @@ pub mod panic_count {
394409
#[inline(never)]
395410
#[cold]
396411
fn is_zero_slow_path() -> bool {
397-
LOCAL_PANIC_COUNT.with(|c| c.get() == 0)
412+
LOCAL_PANIC_COUNT.with(|c| c.get().0 == 0)
398413
}
399414
}
400415

@@ -655,23 +670,22 @@ fn rust_panic_with_hook(
655670
location: &Location<'_>,
656671
can_unwind: bool,
657672
) -> ! {
658-
let (must_abort, panics) = panic_count::increase();
659-
660-
// If this is the third nested call (e.g., panics == 2, this is 0-indexed),
661-
// the panic hook probably triggered the last panic, otherwise the
662-
// double-panic check would have aborted the process. In this case abort the
663-
// process real quickly as we don't want to try calling it again as it'll
664-
// probably just panic again.
665-
if must_abort || panics > 2 {
666-
if panics > 2 {
667-
// Don't try to print the message in this case
668-
// - perhaps that is causing the recursive panics.
669-
rtprintpanic!("thread panicked while processing panic. aborting.\n");
670-
} else {
671-
// Unfortunately, this does not print a backtrace, because creating
672-
// a `Backtrace` will allocate, which we must to avoid here.
673-
let panicinfo = PanicInfo::internal_constructor(message, location, can_unwind);
674-
rtprintpanic!("{panicinfo}\npanicked after panic::always_abort(), aborting.\n");
673+
let must_abort = panic_count::increase(true);
674+
675+
// Check if we need to abort immediately.
676+
if let Some(must_abort) = must_abort {
677+
match must_abort {
678+
panic_count::MustAbort::PanicInHook => {
679+
// Don't try to print the message in this case
680+
// - perhaps that is causing the recursive panics.
681+
rtprintpanic!("thread panicked while processing panic. aborting.\n");
682+
}
683+
panic_count::MustAbort::AlwaysAbort => {
684+
// Unfortunately, this does not print a backtrace, because creating
685+
// a `Backtrace` will allocate, which we must to avoid here.
686+
let panicinfo = PanicInfo::internal_constructor(message, location, can_unwind);
687+
rtprintpanic!("{panicinfo}\npanicked after panic::always_abort(), aborting.\n");
688+
}
675689
}
676690
crate::sys::abort_internal();
677691
}
@@ -697,16 +711,16 @@ fn rust_panic_with_hook(
697711
};
698712
drop(hook);
699713

700-
if panics > 1 || !can_unwind {
701-
// If a thread panics while it's already unwinding then we
702-
// have limited options. Currently our preference is to
703-
// just abort. In the future we may consider resuming
704-
// unwinding or otherwise exiting the thread cleanly.
705-
if !can_unwind {
706-
rtprintpanic!("thread caused non-unwinding panic. aborting.\n");
707-
} else {
708-
rtprintpanic!("thread panicked while panicking. aborting.\n");
709-
}
714+
// Indicate that we have finished executing the panic hook. After this point
715+
// it is fine if there is a panic while executing destructors, as long as it
716+
// it contained within a `catch_unwind`.
717+
panic_count::finished_panic_hook();
718+
719+
if !can_unwind {
720+
// If a thread panics while running destructors or tries to unwind
721+
// through a nounwind function (e.g. extern "C") then we cannot continue
722+
// unwinding and have to abort immediately.
723+
rtprintpanic!("thread caused non-unwinding panic. aborting.\n");
710724
crate::sys::abort_internal();
711725
}
712726

@@ -716,7 +730,7 @@ fn rust_panic_with_hook(
716730
/// This is the entry point for `resume_unwind`.
717731
/// It just forwards the payload to the panic runtime.
718732
pub fn rust_panic_without_hook(payload: Box<dyn Any + Send>) -> ! {
719-
panic_count::increase();
733+
panic_count::increase(false);
720734

721735
struct RewrapBox(Box<dyn Any + Send>);
722736

0 commit comments

Comments
 (0)