pub fn install_signal_handlers() {
#[cfg(target_os = "linux")]
linux::install_signal_handlers_with_backtrace();
}
#[cfg(target_os = "linux")]
mod linux {
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
use std::collections::HashMap;
use std::fs;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
static SIGNAL_RECEIVED: AtomicBool = AtomicBool::new(false);
static BACKTRACE_STORAGE: Mutex<Option<HashMap<i32, String>>> = Mutex::new(None);
pub fn install_signal_handlers_with_backtrace() {
*BACKTRACE_STORAGE.lock().unwrap() = Some(HashMap::new());
install_backtrace_signal_handler();
install_termination_signal_handlers();
}
fn install_termination_signal_handlers() {
extern "C" fn termination_handler(_: libc::c_int) {
if SIGNAL_RECEIVED.swap(true, Ordering::SeqCst) {
return;
}
tracing::error!("Signal received, dumping thread backtraces...");
dump_all_thread_backtraces();
tracing::error!("Backtrace dump complete, terminating process");
std::process::exit(130); }
let handler = SigHandler::Handler(termination_handler);
let action = SigAction::new(handler, SaFlags::empty(), SigSet::empty());
unsafe {
if let Err(e) = sigaction(Signal::SIGINT, &action) {
tracing::error!("Failed to set SIGINT handler: {}", e);
}
if let Err(e) = sigaction(Signal::SIGTERM, &action) {
tracing::error!("Failed to set SIGTERM handler: {}", e);
}
}
}
fn install_backtrace_signal_handler() {
extern "C" fn backtrace_signal_handler(_: libc::c_int) {
let backtrace = std::backtrace::Backtrace::force_capture();
let tid = unsafe { libc::syscall(libc::SYS_gettid) } as i32;
if let Ok(mut storage) = BACKTRACE_STORAGE.lock() {
if let Some(ref mut map) = *storage {
map.insert(tid, format!("{}", backtrace));
}
}
}
let handler = SigHandler::Handler(backtrace_signal_handler);
let action = SigAction::new(handler, SaFlags::empty(), SigSet::empty());
unsafe {
let _ = sigaction(Signal::SIGUSR1, &action);
}
}
fn dump_all_thread_backtraces() {
if let Ok(mut storage) = BACKTRACE_STORAGE.lock() {
if let Some(ref mut map) = *storage {
map.clear();
}
}
let thread_ids = get_all_thread_ids();
tracing::error!("=== Thread Backtrace Dump ===");
tracing::error!("Total threads: {}", thread_ids.len());
tracing::error!("Process ID: {}", std::process::id());
for tid in &thread_ids {
unsafe {
libc::syscall(
libc::SYS_tgkill,
std::process::id() as i32,
*tid,
libc::SIGUSR1,
);
}
}
std::thread::sleep(std::time::Duration::from_millis(100));
let backtraces = BACKTRACE_STORAGE.lock().unwrap();
if let Some(ref map) = *backtraces {
for (i, tid) in thread_ids.iter().enumerate() {
let thread_name = read_thread_name(*tid);
tracing::error!(
"--- Thread {} (TID: {}, Name: {}) ---",
i + 1,
tid,
thread_name
);
if let Some(backtrace) = map.get(tid) {
tracing::error!("Backtrace:\n{}", backtrace);
} else {
tracing::error!("(No backtrace captured for this thread)");
}
}
}
tracing::error!("=== End Thread Backtrace Dump ===");
}
fn get_all_thread_ids() -> Vec<i32> {
let mut thread_ids = Vec::new();
if let Ok(entries) = fs::read_dir("/proc/self/task") {
for entry in entries.flatten() {
if let Ok(file_name) = entry.file_name().into_string() {
if let Ok(tid) = file_name.parse::<i32>() {
thread_ids.push(tid);
}
}
}
}
thread_ids.sort();
thread_ids
}
fn read_thread_name(tid: i32) -> String {
let path = format!("/proc/self/task/{}/comm", tid);
fs::read_to_string(&path)
.map(|s| s.trim().to_string())
.unwrap_or_else(|_| String::from("unknown"))
}
}