use std::future::Future;
use std::time::Duration;
use anyhow::Context;
pub use tokio_util::sync::CancellationToken;
pub const GRACE_PERIOD: Duration = Duration::from_secs(10);
pub async fn run_with_shutdown<F, Fut>(run: F) -> anyhow::Result<()>
where
F: FnOnce(CancellationToken) -> Fut,
Fut: Future<Output = anyhow::Result<()>>,
{
let token = CancellationToken::new();
let service = run(token.clone());
tokio::pin!(service);
tokio::select! {
result = &mut service => result,
result = shutdown_signal() => {
result?;
tracing::info!("Shutdown signal received; cancelling service tasks");
token.cancel();
let Ok(result) = tokio::time::timeout(GRACE_PERIOD, &mut service).await else {
tracing::error!(
grace_period = ?GRACE_PERIOD,
"Graceful shutdown timed out; exiting process",
);
std::process::exit(0);
};
result
},
}
}
pub async fn shutdown_signal() -> anyhow::Result<()> {
#[cfg(unix)]
{
let mut terminate =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.context("failed to install SIGTERM handler")?;
tokio::select! {
_ = terminate.recv() => Ok(()),
result = tokio::signal::ctrl_c() => {
result.context("failed to install Ctrl-C handler")
},
}
}
#[cfg(not(unix))]
{
tokio::signal::ctrl_c().await.context("failed to install Ctrl-C handler")
}
}