turbo_tasks/panic_hooks.rs
1//! Provides a central registry for safe runtime registration and de-registration of panic hooks.
2//!
3//! Registered hooks are called in an arbitrary order.
4//!
5//! This is used inside `turbo-tasks-backend` to invalidate the persistent cache if a panic occurs
6//! anywhere inside of Turbopack. That panic hook must be dynamically registered as it contains a
7//! reference to the database.
8//!
9//! The program using turbo-tasks must call [`std::panic::set_hook`] with [`handle_panic`] exactly
10//! once for these registered panic handlers to function. Short-lived programs or code that does not
11//! fully control its execution environment (like unit tests) may choose not to do this, so these
12//! panic hooks are best-effort.
13//!
14//! It's recommended that when adding this global panic handler (or any other panic handler) that:
15//! - You call it as early in the program as possible, to avoid race conditions with other threads.
16//! - The new panic handler should call any existing panic handler.
17//!
18//! ```
19//! use std::panic::{set_hook, take_hook};
20//! use turbo_tasks::panic_hooks::handle_panic;
21//!
22//! let prev_hook = take_hook();
23//! set_hook(Box::new(move |info| {
24//! handle_panic(info);
25//! prev_hook(info);
26//! }));
27//! ```
28//!
29//! This code is not particularly well-optimized under the assumption that panics are a rare
30//! occurrence.
31
32use std::{
33 cell::RefCell,
34 collections::HashMap,
35 hash::{BuildHasherDefault, DefaultHasher},
36 num::NonZeroU64,
37 panic::PanicHookInfo,
38 sync::{Arc, RwLock},
39};
40
41use crate::util::IdFactory;
42
43thread_local! {
44 /// The location of the last error that occurred in the current thread.
45 ///
46 /// Used for debugging when errors are sent to telemetry.
47 pub(crate) static LAST_ERROR_LOCATION: RefCell<Option<String>> = const { RefCell::new(None) };
48}
49
50static HOOK_ID_FACTORY: IdFactory<NonZeroU64> =
51 IdFactory::new_const(NonZeroU64::MIN, NonZeroU64::MAX);
52
53// We could use a `DashMap` or the `slab` crate, but we anticipate that setting up and tearing down
54// hooks is rare.
55static PANIC_HOOKS: RwLock<HashMap<NonZeroU64, ArcPanicHook, BuildHasherDefault<DefaultHasher>>> =
56 RwLock::new(HashMap::with_hasher(BuildHasherDefault::new()));
57
58pub type PanicHook = Box<dyn Fn(&PanicHookInfo<'_>) + Sync + Send + 'static>;
59pub type ArcPanicHook = Arc<dyn Fn(&PanicHookInfo<'_>) + Sync + Send + 'static>;
60
61/// This function should be registered as the global panic handler using [`std::panic::set_hook`].
62/// See [the module-level documentation][self] for usage examples.
63pub fn handle_panic(info: &PanicHookInfo<'_>) {
64 // we only want to do this once-per-process, so hard-code it here instead of using a dynamically
65 // registered panic hook
66 LAST_ERROR_LOCATION.with_borrow_mut(|loc| {
67 *loc = info.location().map(|l| l.to_string());
68 });
69
70 // Collect and clone all the hooks and drop the lock guard so that we can avoid risks of
71 // deadlocks due to potentially re-entrant calls to `register_panic_hook` or
72 // `PanicHookGuard::drop`. This is expensive, but this should be a cold codepath.
73 let hooks: Vec<ArcPanicHook> = PANIC_HOOKS.read().unwrap().values().cloned().collect();
74 for hook in hooks {
75 (hook)(info);
76 }
77}
78
79/// Registers a hook to be called when a panic occurs. Panic hooks are called in the order that they
80/// are registered. Dropping the returned [`PanicHookGuard`] removes the registered hook.
81///
82/// In the case that the panic hook refers to the object that contains the [`PanicHookGuard`], make
83/// sure to use [`std::sync::Weak`] to avoid leaks. [`Arc::new_cyclic`] may be useful in
84/// constructing such an object.
85pub fn register_panic_hook(hook: PanicHook) -> PanicHookGuard {
86 let id = HOOK_ID_FACTORY.get();
87 PANIC_HOOKS.write().unwrap().insert(id, Arc::from(hook));
88 PanicHookGuard { id }
89}
90
91/// A guard returned from [`register_panic_hook`] that cleans up the panic hook when dropped.
92#[must_use = "If the guard is not stored somewhere, it will be immediately dropped and the panic \
93 hook will be immediately cleaned up"]
94pub struct PanicHookGuard {
95 id: NonZeroU64,
96}
97
98impl Drop for PanicHookGuard {
99 fn drop(&mut self) {
100 PANIC_HOOKS.write().unwrap().remove(&self.id);
101 }
102}