Skip to content

Commit 1b1bf24

Browse files
ijacksonm-ou-se
andcommitted
std panicking: Provide panic::always_abort
We must change the atomic read on panic entry to `Acquire`, to pick up a possible an `always_panic` on another thread. We add `count` to the names of panic_count::get and ::is_zaero, because now there is another reason why panic ought to maybe abort. Renaming these ensures that we have checked every call site to ensure that they don't need further adjustment. Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk> Co-authored-by: Mara Bos <m-ou.se@m-ou.se>
1 parent a9f43a2 commit 1b1bf24

File tree

2 files changed

+85
-15
lines changed

2 files changed

+85
-15
lines changed

library/std/src/panic.rs

+36
Original file line numberDiff line numberDiff line change
@@ -461,5 +461,41 @@ pub fn resume_unwind(payload: Box<dyn Any + Send>) -> ! {
461461
panicking::rust_panic_without_hook(payload)
462462
}
463463

464+
/// Make all future panics abort directly without running the panic hook or unwinding.
465+
///
466+
/// There is no way to undo this; the effect lasts until the process exits or
467+
/// execs (or the equivalent).
468+
///
469+
/// # Use after fork
470+
///
471+
/// This function is particularly useful for calling after `libc::fork`. After `fork`, in a
472+
/// multithreaded program it is (on many platforms) not safe to call the allocator. It is also
473+
/// generally highly undesirable for an unwind to unwind past the `fork`, because that results in
474+
/// the unwind propagating to code that was only ever expecting to run in the parent.
475+
///
476+
/// `panic::always_abort()` helps avoid both of these. It directly avoids any further unwinding,
477+
/// and if there is a panic, the abort will occur without allocating provided that the arguments to
478+
/// panic can be formatted without allocating.
479+
///
480+
/// Examples
481+
///
482+
/// ```no_run
483+
/// #![feature(panic_always_abort)]
484+
/// use std::panic;
485+
///
486+
/// panic::always_abort();
487+
///
488+
/// let _ = panic::catch_unwind(|| {
489+
/// panic!("inside the catch");
490+
/// });
491+
///
492+
/// // We will have aborted already, due to the panic.
493+
/// unreachable!();
494+
/// ```
495+
#[unstable(feature = "panic_always_abort", issue = "84438")]
496+
pub fn always_abort() {
497+
crate::panicking::panic_count::set_always_abort();
498+
}
499+
464500
#[cfg(test)]
465501
mod tests;

library/std/src/panicking.rs

+49-15
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ pub fn take_hook() -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
180180
fn default_hook(info: &PanicInfo<'_>) {
181181
// If this is a double panic, make sure that we print a backtrace
182182
// for this panic. Otherwise only print it if logging is enabled.
183-
let backtrace_env = if panic_count::get() >= 2 {
183+
let backtrace_env = if panic_count::get_count() >= 2 {
184184
RustBacktrace::Print(crate::backtrace_rs::PrintFmt::Full)
185185
} else {
186186
backtrace::rust_backtrace_env()
@@ -233,6 +233,8 @@ pub mod panic_count {
233233
use crate::cell::Cell;
234234
use crate::sync::atomic::{AtomicUsize, Ordering};
235235

236+
pub const ALWAYS_ABORT_FLAG: usize = 1 << (usize::BITS - 1);
237+
236238
// Panic count for the current thread.
237239
thread_local! { static LOCAL_PANIC_COUNT: Cell<usize> = Cell::new(0) }
238240

@@ -241,15 +243,29 @@ pub mod panic_count {
241243
// thread, if that thread currently views `GLOBAL_PANIC_COUNT` as being zero,
242244
// then `LOCAL_PANIC_COUNT` in that thread is zero. This invariant holds before
243245
// and after increase and decrease, but not necessarily during their execution.
246+
//
247+
// Additionally, the top bit of GLOBAL_PANIC_COUNT (GLOBAL_ALWAYS_ABORT_FLAG)
248+
// records whether panic::always_abort() has been called. This can only be
249+
// set, never cleared.
250+
//
251+
// This could be viewed as a struct containing a single bit and an n-1-bit
252+
// value, but if we wrote it like that it would be more than a single word,
253+
// and even a newtype around usize would be clumsy because we need atomics.
254+
// But we use such a tuple for the return type of increase().
255+
//
256+
// Stealing a bit is fine because it just amounts to assuming that each
257+
// panicking thread consumes at least 2 bytes of address space.
244258
static GLOBAL_PANIC_COUNT: AtomicUsize = AtomicUsize::new(0);
245259

246-
pub fn increase() -> usize {
247-
GLOBAL_PANIC_COUNT.fetch_add(1, Ordering::Relaxed);
248-
LOCAL_PANIC_COUNT.with(|c| {
249-
let next = c.get() + 1;
250-
c.set(next);
251-
next
252-
})
260+
pub fn increase() -> (bool, usize) {
261+
(
262+
GLOBAL_PANIC_COUNT.fetch_add(1, Ordering::Acquire) & ALWAYS_ABORT_FLAG != 0,
263+
LOCAL_PANIC_COUNT.with(|c| {
264+
let next = c.get() + 1;
265+
c.set(next);
266+
next
267+
}),
268+
)
253269
}
254270

255271
pub fn decrease() {
@@ -261,13 +277,19 @@ pub mod panic_count {
261277
});
262278
}
263279

264-
pub fn get() -> usize {
280+
pub fn set_always_abort() {
281+
GLOBAL_PANIC_COUNT.fetch_or(ALWAYS_ABORT_FLAG, Ordering::Release);
282+
}
283+
284+
// Disregards ALWAYS_ABORT_FLAG
285+
pub fn get_count() -> usize {
265286
LOCAL_PANIC_COUNT.with(|c| c.get())
266287
}
267288

289+
// Disregards ALWAYS_ABORT_FLAG
268290
#[inline]
269-
pub fn is_zero() -> bool {
270-
if GLOBAL_PANIC_COUNT.load(Ordering::Relaxed) == 0 {
291+
pub fn count_is_zero() -> bool {
292+
if GLOBAL_PANIC_COUNT.load(Ordering::Relaxed) & !ALWAYS_ABORT_FLAG == 0 {
271293
// Fast path: if `GLOBAL_PANIC_COUNT` is zero, all threads
272294
// (including the current one) will have `LOCAL_PANIC_COUNT`
273295
// equal to zero, so TLS access can be avoided.
@@ -410,7 +432,7 @@ pub unsafe fn r#try<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>>
410432
/// Determines whether the current thread is unwinding because of panic.
411433
#[inline]
412434
pub fn panicking() -> bool {
413-
!panic_count::is_zero()
435+
!panic_count::count_is_zero()
414436
}
415437

416438
/// The entry point for panicking with a formatted message.
@@ -563,15 +585,27 @@ fn rust_panic_with_hook(
563585
message: Option<&fmt::Arguments<'_>>,
564586
location: &Location<'_>,
565587
) -> ! {
566-
let panics = panic_count::increase();
588+
let (must_abort, panics) = panic_count::increase();
567589

568590
// If this is the third nested call (e.g., panics == 2, this is 0-indexed),
569591
// the panic hook probably triggered the last panic, otherwise the
570592
// double-panic check would have aborted the process. In this case abort the
571593
// process real quickly as we don't want to try calling it again as it'll
572594
// probably just panic again.
573-
if panics > 2 {
574-
util::dumb_print(format_args!("thread panicked while processing panic. aborting.\n"));
595+
if must_abort || panics > 2 {
596+
if panics > 2 {
597+
// Don't try to print the message in this case
598+
// - perhaps that is causing the recursive panics.
599+
util::dumb_print(format_args!("thread panicked while processing panic. aborting.\n"));
600+
} else {
601+
// Unfortunately, this does not print a backtrace, because creating
602+
// a `Backtrace` will allocate, which we must to avoid here.
603+
let panicinfo = PanicInfo::internal_constructor(message, location);
604+
util::dumb_print(format_args!(
605+
"{}\npanicked after panic::always_abort(), aborting.\n",
606+
panicinfo
607+
));
608+
}
575609
intrinsics::abort()
576610
}
577611

0 commit comments

Comments
 (0)