pub(crate) const SAVE_TIMEOUT_SECS: u64 = 2;
pub(crate) const HOOKS_TIMEOUT_SECS: u64 = 5;
pub(crate) const TEARDOWN_TIMEOUT_SECS: u64 = SAVE_TIMEOUT_SECS + HOOKS_TIMEOUT_SECS;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ShutdownSignal {
Interrupt,
Terminate,
Hangup,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ShutdownAction {
ImmediateExit,
}
pub(crate) fn shutdown_action(_signal: ShutdownSignal) -> ShutdownAction {
ShutdownAction::ImmediateExit
}
pub(crate) fn signal_label(signal: ShutdownSignal) -> &'static str {
match signal {
ShutdownSignal::Interrupt => "interrupt",
ShutdownSignal::Terminate => "terminate",
ShutdownSignal::Hangup => "hangup",
}
}
pub(crate) struct SignalHandle {
#[cfg(unix)]
inner: signal_hook::iterator::Handle,
}
impl SignalHandle {
pub(crate) fn close(self) {
#[cfg(unix)]
self.inner.close();
}
}
#[cfg(unix)]
pub(crate) fn spawn_shutdown_signal_task(
tx: tokio::sync::mpsc::UnboundedSender<ShutdownSignal>,
) -> SignalHandle {
use signal_hook::consts::signal::{SIGHUP, SIGINT, SIGTERM};
use signal_hook::iterator::Signals;
let mut signals = Signals::new([SIGTERM, SIGHUP, SIGINT])
.expect("failed to register signal hooks");
let handle = signals.handle();
std::thread::Builder::new()
.name("signal-listener".into())
.spawn(move || {
for sig in signals.forever() {
tracing::debug!(sig, "signal-listener: received signal");
let shutdown = match sig {
SIGINT => ShutdownSignal::Interrupt,
SIGTERM => ShutdownSignal::Terminate,
SIGHUP => ShutdownSignal::Hangup,
_ => continue,
};
let _ = tx.send(shutdown);
break;
}
})
.expect("failed to spawn signal-listener thread");
SignalHandle { inner: handle }
}
#[cfg(not(unix))]
pub(crate) fn spawn_shutdown_signal_task(
tx: tokio::sync::mpsc::UnboundedSender<ShutdownSignal>,
) -> SignalHandle {
tokio::spawn(async move {
let _ = tokio::signal::ctrl_c().await;
super::lifecycle::emergency_teardown_terminal();
let _ = tx.send(ShutdownSignal::Interrupt);
});
SignalHandle {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn labels_are_human_readable() {
assert_eq!(signal_label(ShutdownSignal::Interrupt), "interrupt");
assert_eq!(signal_label(ShutdownSignal::Terminate), "terminate");
assert_eq!(signal_label(ShutdownSignal::Hangup), "hangup");
}
#[test]
fn all_signals_are_immediate_exit() {
assert_eq!(shutdown_action(ShutdownSignal::Terminate), ShutdownAction::ImmediateExit);
assert_eq!(shutdown_action(ShutdownSignal::Hangup), ShutdownAction::ImmediateExit);
assert_eq!(shutdown_action(ShutdownSignal::Interrupt), ShutdownAction::ImmediateExit);
}
#[test]
fn all_signals_have_labels() {
for sig in [ShutdownSignal::Interrupt, ShutdownSignal::Terminate, ShutdownSignal::Hangup] {
assert!(!signal_label(sig).is_empty());
}
}
#[test]
fn teardown_budget_is_sum_of_parts() {
assert_eq!(TEARDOWN_TIMEOUT_SECS, SAVE_TIMEOUT_SECS + HOOKS_TIMEOUT_SECS);
}
}