#[cfg(unix)]
struct ShutdownSignals {
interrupt: tokio::signal::unix::Signal,
terminate: tokio::signal::unix::Signal,
hangup: tokio::signal::unix::Signal,
suspend: tokio::signal::unix::Signal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TerminalSignal {
Shutdown,
Suspend,
}
#[cfg(unix)]
impl ShutdownSignals {
fn install() -> std::io::Result<Self> {
use tokio::signal::unix::{SignalKind, signal};
Ok(Self {
interrupt: signal(SignalKind::interrupt())?,
terminate: signal(SignalKind::terminate())?,
hangup: signal(SignalKind::hangup())?,
suspend: signal(SignalKind::from_raw(rustix::process::Signal::TSTP.as_raw()))?,
})
}
async fn recv(&mut self) -> TerminalSignal {
tokio::select! {
_ = self.interrupt.recv() => TerminalSignal::Shutdown,
_ = self.terminate.recv() => TerminalSignal::Shutdown,
_ = self.hangup.recv() => TerminalSignal::Shutdown,
_ = self.suspend.recv() => TerminalSignal::Suspend,
}
}
}
#[cfg(unix)]
pub fn suspend_current_process() -> std::io::Result<()> {
Ok(rustix::process::kill_process(
rustix::process::getpid(),
rustix::process::Signal::STOP,
)?)
}
#[cfg(not(unix))]
pub fn suspend_current_process() -> std::io::Result<()> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"process suspension is unavailable on this platform",
))
}
pub struct Shutdown {
#[cfg(unix)]
signals: Option<ShutdownSignals>,
}
impl Shutdown {
pub fn install() -> Self {
Self {
#[cfg(unix)]
signals: ShutdownSignals::install().ok(),
}
}
pub async fn recv(&mut self) -> TerminalSignal {
#[cfg(unix)]
if let Some(signals) = self.signals.as_mut() {
return signals.recv().await;
}
std::future::pending().await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn the_shutdown_arm_does_not_fire_on_its_own() {
let mut shutdown = Shutdown::install();
let fired =
tokio::time::timeout(std::time::Duration::from_millis(50), shutdown.recv()).await;
assert!(
fired.is_err(),
"the shutdown arm resolved with no signal delivered"
);
}
}