use std::panic::{self, AssertUnwindSafe, Location};
use std::sync::{Mutex, PoisonError};
use std::{any, io, mem, process};
#[track_caller]
pub fn add_shutdown_hook<F: FnOnce()>(func: F)
where
F: Send + 'static,
{
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");
std::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) {
loop {
let res = unsafe { libc::write(libc::STDERR_FILENO, s.as_ptr().cast(), s.len()) };
if res >= 0 || io::Error::last_os_error().kind() != io::ErrorKind::Interrupted {
break;
}
}
}