use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
pub const DEFAULT_SHUTDOWN_TIMEOUT_SECS: i32 = 10;
static SIGNAL_HANDLER_INSTALLED: AtomicBool = AtomicBool::new(false);
#[cfg(unix)]
pub(crate) fn install_signal_handler<F, Fut>(shutdown_callback: F)
where
F: FnOnce() -> Fut + Send + 'static,
Fut: std::future::Future<Output = ()> + Send + 'static,
{
use signal_hook::consts::signal::{SIGINT, SIGTERM};
use signal_hook::iterator::Signals;
if SIGNAL_HANDLER_INSTALLED
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_err()
{
return;
}
std::thread::Builder::new()
.name("boxlite-signal-handler".into())
.spawn(move || {
let mut signals = match Signals::new([SIGTERM, SIGINT]) {
Ok(s) => s,
Err(e) => {
tracing::error!("Failed to register signal handlers: {}", e);
SIGNAL_HANDLER_INSTALLED.store(false, Ordering::SeqCst);
return;
}
};
for sig in signals.forever() {
match sig {
SIGTERM => {
tracing::info!("Received SIGTERM, initiating graceful shutdown");
}
SIGINT => {
tracing::info!("Received SIGINT, initiating graceful shutdown");
}
_ => continue,
}
break;
}
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create shutdown runtime");
rt.block_on(shutdown_callback());
std::process::exit(0);
})
.expect("Failed to spawn signal handler thread");
}
#[cfg(not(unix))]
pub(crate) fn install_signal_handler<F, Fut>(_shutdown_callback: F)
where
F: FnOnce() -> Fut + Send + 'static,
Fut: std::future::Future<Output = ()> + Send + 'static,
{
tracing::warn!("Signal handling not implemented for this platform");
}
pub(crate) fn timeout_to_duration(timeout: Option<i32>) -> Option<Duration> {
match timeout {
None => Some(Duration::from_secs(DEFAULT_SHUTDOWN_TIMEOUT_SECS as u64)),
Some(-1) => None, Some(secs) if secs > 0 => Some(Duration::from_secs(secs as u64)),
Some(_) => Some(Duration::from_secs(DEFAULT_SHUTDOWN_TIMEOUT_SECS as u64)), }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timeout_to_duration_default() {
let duration = timeout_to_duration(None);
assert_eq!(duration, Some(Duration::from_secs(10)));
}
#[test]
fn test_timeout_to_duration_custom() {
let duration = timeout_to_duration(Some(30));
assert_eq!(duration, Some(Duration::from_secs(30)));
}
#[test]
fn test_timeout_to_duration_infinite() {
let duration = timeout_to_duration(Some(-1));
assert_eq!(duration, None);
}
#[test]
fn test_timeout_to_duration_invalid() {
let duration = timeout_to_duration(Some(0));
assert_eq!(duration, Some(Duration::from_secs(10)));
let duration = timeout_to_duration(Some(-5));
assert_eq!(duration, Some(Duration::from_secs(10)));
}
}