use std::sync::atomic::Ordering;
pub fn register_shutdown_handler() {
if let Err(e) = ctrlc::set_handler(move || {
let prev = crate::SIGNAL_COUNT.fetch_add(1, Ordering::AcqRel);
if prev == 0 {
crate::SHUTDOWN.store(true, Ordering::Release);
crate::SIGNAL_NUMBER.store(2, Ordering::Release);
crate::cancel_token().cancel();
use std::io::Write;
let _ = writeln!(
std::io::stderr(),
"shutdown signal received; finishing current operation gracefully"
);
} else {
std::process::exit(130);
}
}) {
tracing::warn!(target: "signals", error = %e, "signal handler registration failed");
}
}
#[cfg(test)]
mod tests {
#[test]
fn handler_source_has_no_panicking_io() {
let source = include_str!("signals.rs");
let closure_start = source
.find("ctrlc::set_handler")
.expect("handler registration must exist");
let closure_end = closure_start
+ source[closure_start..]
.find("std::process::exit(130)")
.expect("forced-exit path must exist");
let closure_body = &source[closure_start..closure_end];
assert!(
!closure_body.contains("eprintln!"),
"signal closure must not use eprintln! (BrokenPipe panic, G42/C2)"
);
assert!(
!closure_body.contains("tracing::"),
"signal closure must not use tracing (stderr I/O can panic, G42/C2)"
);
assert!(
closure_body.contains("let _ = writeln!"),
"first-signal notice must be a best-effort write"
);
}
}