use std::panic::{self, AssertUnwindSafe, Location};
use std::sync::{Mutex, PoisonError};
use std::{any, mem, process};
#[track_caller]
pub fn add_shutdown_hook<F: FnOnce() + Send + 'static>(func: F) {
SHUTDOWN_HOOKS
.lock()
.unwrap_or_else(PoisonError::into_inner)
.push(ShutdownHook { source: Location::caller(), callback: Box::new(func) });
}
pub(super) fn register_shutdown_hook() {
unsafe {
libc::atexit(run_shutdown_hooks);
}
}
extern "C" fn run_shutdown_hooks() {
let guard = PanicGuard;
let mut any_panicked = false;
let mut hooks = SHUTDOWN_HOOKS.lock().unwrap_or_else(PoisonError::into_inner);
for hook in mem::take(&mut *hooks).into_iter().rev() {
any_panicked |= hook.run().is_err();
}
if any_panicked {
write_stderr("error: one or more shutdown hooks panicked (see `stderr` for details).\n");
process::abort()
}
mem::forget(guard);
}
struct PanicGuard;
impl Drop for PanicGuard {
fn drop(&mut self) {
write_stderr("Failed to catch panic in the `atexit` callback, aborting!\n");
process::abort();
}
}
static SHUTDOWN_HOOKS: Mutex<Vec<ShutdownHook>> = Mutex::new(Vec::new());
struct ShutdownHook {
source: &'static Location<'static>,
callback: Box<dyn FnOnce() + Send>,
}
impl ShutdownHook {
fn run(self) -> Result<(), ()> {
let Self { source, callback } = self;
let result = panic::catch_unwind(AssertUnwindSafe(callback));
if let Err(e) = result {
let msg = failure_message(&e);
write_stderr(&format!(
"error: shutdown hook (registered at {source}) panicked: {msg}\n"
));
Err(())
} else {
Ok(())
}
}
}
fn failure_message(e: &(dyn any::Any + Send)) -> &str {
if let Some(&msg) = e.downcast_ref::<&'static str>() {
msg
} else if let Some(msg) = e.downcast_ref::<String>() {
msg.as_str()
} else {
"<panic payload of unknown type>"
}
}
fn write_stderr(s: &str) {
use std::io::Write;
let _ = std::io::stderr().lock().write_all(s.as_bytes());
}