use std::{
io::Write,
marker::PhantomData,
panic::{self, PanicHookInfo},
rc::Rc,
sync::{Arc, Mutex},
thread,
};
type PanicHook = dyn Fn(&PanicHookInfo<'_>) + Send + Sync + 'static;
#[derive(Default)]
struct BackgroundPanicReport {
count: usize,
first_location: Option<String>,
}
pub(in crate::tui) struct TerminalPanicHook {
previous: Arc<PanicHook>,
background_panics: Arc<Mutex<Option<BackgroundPanicReport>>>,
_owner_only: PhantomData<Rc<()>>,
}
impl TerminalPanicHook {
pub(in crate::tui) fn install() -> Self {
Self::install_with_restore(super::restore_terminal_best_effort)
}
fn install_with_restore(restore: impl Fn() + Send + Sync + 'static) -> Self {
let owner = thread::current().id();
let previous: Arc<PanicHook> = panic::take_hook().into();
let background_panics = Arc::new(Mutex::new(Some(BackgroundPanicReport::default())));
let hook_previous = Arc::clone(&previous);
let hook_panics = Arc::clone(&background_panics);
panic::set_hook(Box::new(move |info| {
let active = {
let mut panics = hook_panics.lock().unwrap_or_else(|e| e.into_inner());
if let Some(report) = panics.as_mut() {
if cfg!(panic = "unwind") && thread::current().id() != owner {
report.count = report.count.saturating_add(1);
if report.first_location.is_none() {
report.first_location = info.location().map(|location| {
let file: String = location.file().chars().take(256).collect();
format!("{file}:{}", location.line())
});
}
return;
}
true
} else {
false
}
};
if active {
restore();
}
hook_previous(info);
}));
Self {
previous,
background_panics,
_owner_only: PhantomData,
}
}
}
impl Drop for TerminalPanicHook {
fn drop(&mut self) {
let report = self
.background_panics
.lock()
.unwrap_or_else(|e| e.into_inner())
.take()
.unwrap_or_default();
if !thread::panicking() {
let previous = Arc::clone(&self.previous);
panic::set_hook(Box::new(move |info| previous(info)));
}
if report.count > 0 {
let location = report
.first_location
.as_deref()
.unwrap_or("unknown location");
let _ = writeln!(
std::io::stderr(),
"Mission Control: {} background worker panic(s) occurred; first at {location}.",
report.count
);
}
}
}