use std::time::Duration;
use tokio_util::sync::CancellationToken;
pub const GRACE: Duration = Duration::from_secs(30);
pub async fn graceful<F>(shutdown: CancellationToken, join_future: F) -> F::Output
where
F: std::future::Future,
{
let mut pinned: std::pin::Pin<Box<F>> = Box::pin(join_future);
tokio::select! {
biased;
output = &mut pinned => {
return output;
}
_ = shutdown.cancelled() => {
tracing::info_span!("daemon").in_scope(|| {
tracing::info!(
grace_ms = GRACE.as_millis() as u64,
"shutdown token tripped; entering graceful shutdown",
);
});
}
}
let result = tokio::time::timeout(GRACE, pinned.as_mut()).await;
match result {
Ok(val) => val,
Err(_timeout) => {
tracing::info_span!("daemon").in_scope(|| {
tracing::warn!(
grace_ms = GRACE.as_millis() as u64,
"graceful shutdown deadline expired; waiting for runtime to finish",
);
});
pinned.await
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn graceful_returns_ready_future_even_if_shutdown_already_cancelled() {
let shutdown = CancellationToken::new();
shutdown.cancel();
let output = graceful(shutdown, async { "finished" }).await;
assert_eq!(output, "finished");
}
}