Skip to content

Commit f03cf99

Browse files
committed
Add a fast path for std::thread::panicking.
This is done by adding a global atomic variable (non-TLS) that counts how many threads are panicking. In order to check if the current thread is panicking, this variable is read and, if it is zero, no thread (including the one where `panicking` is being called) is panicking and `panicking` can return `false` immediately without needing to access TLS. If the global counter is not zero, the local counter is accessed from TLS to check if the current thread is panicking.
1 parent cb272d5 commit f03cf99

File tree

2 files changed

+51
-14
lines changed

2 files changed

+51
-14
lines changed

src/libstd/panicking.rs

+50-13
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ pub fn take_hook() -> Box<dyn Fn(&PanicInfo<'_>) + 'static + Sync + Send> {
170170
fn default_hook(info: &PanicInfo<'_>) {
171171
// If this is a double panic, make sure that we print a backtrace
172172
// for this panic. Otherwise only print it if logging is enabled.
173-
let backtrace_env = if update_panic_count(0) >= 2 {
173+
let backtrace_env = if panic_count::get() >= 2 {
174174
RustBacktrace::Print(backtrace_rs::PrintFmt::Full)
175175
} else {
176176
backtrace::rust_backtrace_env()
@@ -222,19 +222,56 @@ fn default_hook(info: &PanicInfo<'_>) {
222222
#[cfg(not(test))]
223223
#[doc(hidden)]
224224
#[unstable(feature = "update_panic_count", issue = "none")]
225-
pub fn update_panic_count(amt: isize) -> usize {
225+
pub mod panic_count {
226226
use crate::cell::Cell;
227-
thread_local! { static PANIC_COUNT: Cell<usize> = Cell::new(0) }
227+
use crate::sync::atomic::{AtomicUsize, Ordering};
228+
229+
// Panic count for the current thread.
230+
thread_local! { static LOCAL_PANIC_COUNT: Cell<usize> = Cell::new(0) }
231+
232+
// Sum of panic counts from all threads. The purpose of this is to have
233+
// a fast path in `is_zero` (which is used by `panicking`). Access to
234+
// this variable can be always be done with relaxed ordering because
235+
// it is always guaranteed that, if `GLOBAL_PANIC_COUNT` is zero,
236+
// `LOCAL_PANIC_COUNT` will be zero.
237+
static GLOBAL_PANIC_COUNT: AtomicUsize = AtomicUsize::new(0);
238+
239+
pub fn increase() -> usize {
240+
GLOBAL_PANIC_COUNT.fetch_add(1, Ordering::Relaxed);
241+
LOCAL_PANIC_COUNT.with(|c| {
242+
let next = c.get() + 1;
243+
c.set(next);
244+
next
245+
})
246+
}
247+
248+
pub fn decrease() -> usize {
249+
GLOBAL_PANIC_COUNT.fetch_sub(1, Ordering::Relaxed);
250+
LOCAL_PANIC_COUNT.with(|c| {
251+
let next = c.get() - 1;
252+
c.set(next);
253+
next
254+
})
255+
}
228256

229-
PANIC_COUNT.with(|c| {
230-
let next = (c.get() as isize + amt) as usize;
231-
c.set(next);
232-
next
233-
})
257+
pub fn get() -> usize {
258+
LOCAL_PANIC_COUNT.with(|c| c.get())
259+
}
260+
261+
pub fn is_zero() -> bool {
262+
if GLOBAL_PANIC_COUNT.load(Ordering::Relaxed) == 0 {
263+
// Fast path: if `GLOBAL_PANIC_COUNT` is zero, all threads
264+
// (including the current one) will have `LOCAL_PANIC_COUNT`
265+
// equal to zero, so TLS access can be avoided.
266+
true
267+
} else {
268+
LOCAL_PANIC_COUNT.with(|c| c.get() == 0)
269+
}
270+
}
234271
}
235272

236273
#[cfg(test)]
237-
pub use realstd::rt::update_panic_count;
274+
pub use realstd::rt::panic_count;
238275

239276
/// Invoke a closure, capturing the cause of an unwinding panic if one occurs.
240277
pub unsafe fn r#try<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>> {
@@ -284,7 +321,7 @@ pub unsafe fn r#try<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>>
284321
#[cold]
285322
unsafe fn cleanup(payload: *mut u8) -> Box<dyn Any + Send + 'static> {
286323
let obj = Box::from_raw(__rust_panic_cleanup(payload));
287-
update_panic_count(-1);
324+
panic_count::decrease();
288325
obj
289326
}
290327

@@ -314,7 +351,7 @@ pub unsafe fn r#try<R, F: FnOnce() -> R>(f: F) -> Result<R, Box<dyn Any + Send>>
314351

315352
/// Determines whether the current thread is unwinding because of panic.
316353
pub fn panicking() -> bool {
317-
update_panic_count(0) != 0
354+
!panic_count::is_zero()
318355
}
319356

320357
/// The entry point for panicking with a formatted message.
@@ -452,7 +489,7 @@ fn rust_panic_with_hook(
452489
message: Option<&fmt::Arguments<'_>>,
453490
location: &Location<'_>,
454491
) -> ! {
455-
let panics = update_panic_count(1);
492+
let panics = panic_count::increase();
456493

457494
// If this is the third nested call (e.g., panics == 2, this is 0-indexed),
458495
// the panic hook probably triggered the last panic, otherwise the
@@ -514,7 +551,7 @@ fn rust_panic_with_hook(
514551
/// This is the entry point for `resume_unwind`.
515552
/// It just forwards the payload to the panic runtime.
516553
pub fn rust_panic_without_hook(payload: Box<dyn Any + Send>) -> ! {
517-
update_panic_count(1);
554+
panic_count::increase();
518555

519556
struct RewrapBox(Box<dyn Any + Send>);
520557

src/libstd/rt.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#![doc(hidden)]
1616

1717
// Re-export some of our utilities which are expected by other crates.
18-
pub use crate::panicking::{begin_panic, begin_panic_fmt, update_panic_count};
18+
pub use crate::panicking::{begin_panic, begin_panic_fmt, panic_count};
1919

2020
// To reduce the generated code of the new `lang_start`, this function is doing
2121
// the real work.

0 commit comments

Comments
 (0)