use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Once;
static SHUTDOWN: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Action {
SetFlag,
Default,
}
fn action(already_set: bool) -> Action {
if already_set {
Action::Default
} else {
Action::SetFlag
}
}
#[cfg(any(windows, test))]
const CTRL_C: u32 = 0;
#[cfg(any(windows, test))]
const CTRL_BREAK: u32 = 1;
#[cfg(windows)]
const _: () = {
use windows_sys::Win32::System::Console::{CTRL_BREAK_EVENT, CTRL_C_EVENT};
assert!(CTRL_C == CTRL_C_EVENT && CTRL_BREAK == CTRL_BREAK_EVENT);
};
#[cfg(any(windows, test))]
fn is_termination_event(event: u32) -> bool {
event == CTRL_C || event == CTRL_BREAK
}
#[no_mangle]
pub extern "C" fn nova_rt_shutdown_requested() -> i8 {
install();
i8::from(SHUTDOWN.load(Ordering::SeqCst))
}
fn install() {
static INSTALL: Once = Once::new();
INSTALL.call_once(install_handler);
}
#[cfg(unix)]
fn install_handler() {
unsafe {
let mut sa: libc::sigaction = std::mem::zeroed();
sa.sa_sigaction = unix_handler as extern "C" fn(libc::c_int) as libc::sighandler_t;
sa.sa_flags = libc::SA_RESTART;
libc::sigemptyset(&mut sa.sa_mask);
libc::sigaction(libc::SIGTERM, &sa, std::ptr::null_mut());
let mut old: libc::sigaction = std::mem::zeroed();
libc::sigaction(libc::SIGINT, std::ptr::null(), &mut old);
if old.sa_sigaction != libc::SIG_IGN {
libc::sigaction(libc::SIGINT, &sa, std::ptr::null_mut());
}
}
}
#[cfg(unix)]
extern "C" fn unix_handler(sig: libc::c_int) {
if action(SHUTDOWN.swap(true, Ordering::SeqCst)) == Action::Default {
unsafe {
libc::signal(sig, libc::SIG_DFL);
libc::raise(sig);
}
}
}
#[cfg(windows)]
fn install_handler() {
use windows_sys::Win32::System::Console::SetConsoleCtrlHandler;
let _ = unsafe {
SetConsoleCtrlHandler(
Some(console_handler as unsafe extern "system" fn(u32) -> windows_sys::core::BOOL),
1,
)
};
}
#[cfg(windows)]
pub extern "system" fn console_handler(event: u32) -> windows_sys::core::BOOL {
if !is_termination_event(event) {
return 0;
}
i32::from(action(SHUTDOWN.swap(true, Ordering::SeqCst)) == Action::SetFlag)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_first_request_sets_the_flag_and_a_later_one_takes_the_default_action() {
assert_eq!(action(false), Action::SetFlag);
assert_eq!(action(true), Action::Default);
}
#[test]
fn only_ctrl_c_and_ctrl_break_ask_for_a_graceful_shutdown() {
for (event, expected) in [
(CTRL_C, true),
(CTRL_BREAK, true),
(2, false),
(5, false),
(6, false),
] {
assert_eq!(is_termination_event(event), expected, "event {event}");
}
}
}