use std::future::Future;
pub fn termination_signal() -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
Box::pin(async {
#[cfg(unix)]
{
unix_termination().await;
}
#[cfg(windows)]
{
windows_termination().await;
}
#[cfg(not(any(unix, windows)))]
{
std::future::pending::<()>().await;
}
})
}
#[cfg(unix)]
async fn unix_termination() {
use tokio::signal::unix::{SignalKind, signal};
let sigterm = signal(SignalKind::terminate());
let sigint = signal(SignalKind::interrupt());
match (sigterm, sigint) {
(Ok(mut term), Ok(mut int)) => {
tokio::select! {
_ = term.recv() => {}
_ = int.recv() => {}
}
}
(Ok(mut term), Err(error)) => {
eprintln!("warning: cannot install SIGINT handler: {error}");
term.recv().await;
}
(Err(error), Ok(mut int)) => {
eprintln!("warning: cannot install SIGTERM handler: {error}");
int.recv().await;
}
(Err(term_err), Err(int_err)) => {
eprintln!(
"warning: cannot install SIGTERM ({term_err}) or SIGINT ({int_err}) handler; \
blocking until external termination"
);
std::future::pending::<()>().await;
}
}
}
#[cfg(windows)]
async fn windows_termination() {
use tokio::signal::windows::{ctrl_break, ctrl_c};
let mut ctrl_c = match ctrl_c() {
Ok(stream) => stream,
Err(error) => {
eprintln!("warning: cannot install Ctrl-C handler: {error}");
std::future::pending::<()>().await;
return;
}
};
let mut ctrl_break = match ctrl_break() {
Ok(stream) => stream,
Err(error) => {
eprintln!("warning: cannot install Ctrl-Break handler: {error}");
ctrl_c.recv().await;
return;
}
};
tokio::select! {
_ = ctrl_c.recv() => {}
_ = ctrl_break.recv() => {}
}
}
#[cfg(test)]
mod tests {
use super::termination_signal;
use std::time::Duration;
#[test]
fn termination_signal_is_send_static() {
fn assert_send_static<T: Future<Output = ()> + Send + 'static>() {}
assert_send_static::<std::pin::Pin<Box<dyn Future<Output = ()> + Send + 'static>>>();
let _signal = termination_signal();
}
#[tokio::test]
async fn termination_signal_waits_for_signal() {
let signal = termination_signal();
let result = tokio::time::timeout(Duration::from_millis(50), signal).await;
assert!(result.is_err(), "signal future should wait, not resolve");
}
}