use std::future::Future;
pub async fn shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{signal, SignalKind};
let mut sigterm =
signal(SignalKind::terminate()).expect("Failed to install SIGTERM handler");
let mut sigint = signal(SignalKind::interrupt()).expect("Failed to install SIGINT handler");
let mut sigquit = signal(SignalKind::quit()).expect("Failed to install SIGQUIT handler");
tokio::select! {
_ = sigterm.recv() => {}
_ = sigint.recv() => {}
_ = sigquit.recv() => {}
}
}
#[cfg(windows)]
{
use tokio::signal::windows;
let mut ctrl_c = windows::ctrl_c().expect("Failed to install Ctrl+C handler");
let mut ctrl_close = windows::ctrl_close().expect("Failed to install Ctrl+Close handler");
let mut ctrl_break = windows::ctrl_break().expect("Failed to install Ctrl+Break handler");
let mut ctrl_shutdown =
windows::ctrl_shutdown().expect("Failed to install Ctrl+Shutdown handler");
let mut ctrl_logoff =
windows::ctrl_logoff().expect("Failed to install Ctrl+Logoff handler");
tokio::select! {
_ = ctrl_c.recv() => {}
_ = ctrl_close.recv() => {}
_ = ctrl_break.recv() => {}
_ = ctrl_shutdown.recv() => {}
_ = ctrl_logoff.recv() => {}
}
}
#[cfg(not(any(unix, windows)))]
{
std::future::pending::<()>().await;
}
}
pub async fn shutdown_with_token(token: tokio_util::sync::CancellationToken) {
shutdown_signal().await;
token.cancel();
}
pub fn shutdown_future() -> impl Future<Output = ()> {
shutdown_signal()
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use tokio_util::sync::CancellationToken;
#[tokio::test]
async fn test_shutdown_with_token_immediate_cancel() {
let token = CancellationToken::new();
let token_clone = token.clone();
let _handle = tokio::spawn(async move {
let _ = token_clone;
});
token.cancel();
assert!(token.is_cancelled());
}
#[tokio::test]
async fn test_shutdown_future_is_send() {
fn assert_send<T: Send>(_t: T) {}
let fut = shutdown_future();
assert_send(fut);
}
#[tokio::test]
async fn test_shutdown_signal_does_not_block_indefinitely_in_select() {
tokio::select! {
_ = shutdown_signal() => {
}
_ = tokio::time::sleep(Duration::from_millis(10)) => {
}
}
}
#[tokio::test]
async fn test_shutdown_with_token_cancels_token() {
let token = CancellationToken::new();
let _fut = shutdown_with_token(token.clone());
}
#[tokio::test]
async fn test_cancellation_token_basic() {
let token = CancellationToken::new();
assert!(!token.is_cancelled());
let token_clone = token.clone();
token_clone.cancel();
assert!(token.is_cancelled());
}
#[tokio::test]
async fn test_cancellation_token_cancelled_completes_after_cancel() {
let token = CancellationToken::new();
let token_clone = token.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(10)).await;
token_clone.cancel();
});
token.cancelled().await;
}
#[tokio::test]
async fn test_multiple_listeners_all_complete() {
let token = CancellationToken::new();
let t1 = token.clone();
let t2 = token.clone();
let t3 = token.clone();
let h1 = tokio::spawn(async move {
t1.cancelled().await;
1
});
let h2 = tokio::spawn(async move {
t2.cancelled().await;
2
});
let h3 = tokio::spawn(async move {
t3.cancelled().await;
3
});
tokio::time::sleep(Duration::from_millis(10)).await;
token.cancel();
assert_eq!(h1.await.unwrap(), 1);
assert_eq!(h2.await.unwrap(), 2);
assert_eq!(h3.await.unwrap(), 3);
}
#[tokio::test]
async fn test_child_token_cancellation() {
let parent = CancellationToken::new();
let child = parent.child_token();
assert!(!parent.is_cancelled());
assert!(!child.is_cancelled());
parent.cancel();
assert!(parent.is_cancelled());
assert!(child.is_cancelled());
}
#[tokio::test]
async fn test_child_token_does_not_cancel_parent() {
let parent = CancellationToken::new();
let child = parent.child_token();
child.cancel();
assert!(!parent.is_cancelled());
assert!(child.is_cancelled());
}
#[tokio::test]
async fn test_select_with_token_and_other_future() {
let token = CancellationToken::new();
let token_clone = token.clone();
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(10)).await;
token_clone.cancel();
});
tokio::select! {
_ = token.cancelled() => {
}
_ = tokio::time::sleep(Duration::from_secs(1)) => {
panic!("token cancellation should win");
}
}
}
#[tokio::test]
async fn test_nested_child_tokens() {
let root = CancellationToken::new();
let level1 = root.child_token();
let level2 = level1.child_token();
let level3 = level2.child_token();
root.cancel();
assert!(root.is_cancelled());
assert!(level1.is_cancelled());
assert!(level2.is_cancelled());
assert!(level3.is_cancelled());
}
}