use std::io;
pub(crate) struct Terminate {
#[cfg(unix)]
signal: tokio::signal::unix::Signal,
#[cfg(windows)]
ctrl_c: tokio::signal::windows::CtrlC,
#[cfg(windows)]
ctrl_break: tokio::signal::windows::CtrlBreak,
#[cfg(windows)]
ctrl_close: tokio::signal::windows::CtrlClose,
#[cfg(windows)]
ctrl_shutdown: tokio::signal::windows::CtrlShutdown,
#[cfg(windows)]
ctrl_logoff: tokio::signal::windows::CtrlLogoff,
}
impl core::fmt::Debug for Terminate {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Terminate").finish_non_exhaustive()
}
}
impl Terminate {
pub(crate) fn install() -> io::Result<Self> {
#[cfg(unix)]
{
Ok(Self {
signal: tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?,
})
}
#[cfg(windows)]
{
use tokio::signal::windows;
Ok(Self {
ctrl_c: windows::ctrl_c()?,
ctrl_break: windows::ctrl_break()?,
ctrl_close: windows::ctrl_close()?,
ctrl_shutdown: windows::ctrl_shutdown()?,
ctrl_logoff: windows::ctrl_logoff()?,
})
}
}
pub(crate) async fn recv(&mut self) -> Option<()> {
#[cfg(unix)]
{
self.signal.recv().await
}
#[cfg(windows)]
{
tokio::select! {
received = self.ctrl_c.recv() => received,
received = self.ctrl_break.recv() => received,
received = self.ctrl_close.recv() => received,
received = self.ctrl_shutdown.recv() => received,
received = self.ctrl_logoff.recv() => received,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn a_terminate_listener_installs_on_this_platform() {
let listener = Terminate::install().expect("installing a terminate listener must work");
assert!(format!("{listener:?}").contains("Terminate"));
}
#[tokio::test]
async fn an_uninvoked_listener_does_not_resolve() {
let mut listener = Terminate::install().unwrap();
let early =
tokio::time::timeout(std::time::Duration::from_millis(150), listener.recv()).await;
assert!(
early.is_err(),
"recv() must park until the OS actually asks us to stop"
);
}
}