use std::time::Duration;
#[derive(Debug, Clone)]
pub struct GracefulShutdownConfig {
pub drain_timeout: Duration,
}
impl Default for GracefulShutdownConfig {
fn default() -> Self {
Self {
drain_timeout: Duration::from_secs(30),
}
}
}
impl GracefulShutdownConfig {
pub fn with_drain_timeout(timeout: Duration) -> Self {
Self {
drain_timeout: timeout,
}
}
}
pub async fn default_shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
let term = signal(SignalKind::terminate()).ok();
let int = tokio::signal::ctrl_c();
match term {
Some(mut term) => {
tokio::select! {
_ = term.recv() => {}
_ = int => {}
}
}
None => {
let _ = int.await;
}
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
}
fn run_stop_hooks() {
#[cfg(feature = "kit")]
if let Some(kit) = crate::integrations::kit::take_ready_kit() {
let _ = std::thread::spawn(move || {
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build();
if let Ok(rt) = rt {
rt.block_on(kit.shutdown_async());
}
})
.join();
}
}
async fn run_lifecycle_stop_hooks() {
#[cfg(feature = "lifecycle")]
crate::lifecycle::run_on_stop().await;
}
async fn run_lifecycle_start_hooks() {
#[cfg(feature = "lifecycle")]
crate::lifecycle::run_on_start().await;
}
pub async fn serve_with_graceful_shutdown(
router: axum::Router,
listener: tokio::net::TcpListener,
shutdown: impl std::future::Future<Output = ()> + Send + 'static,
config: GracefulShutdownConfig,
) -> std::io::Result<()> {
let (trigger_tx, mut trigger_rx) = tokio::sync::watch::channel(false);
let axum_shutdown = async move {
shutdown.await;
let _ = trigger_tx.send(true);
};
run_lifecycle_start_hooks().await;
let server = axum::serve(listener, router).with_graceful_shutdown(axum_shutdown);
let drain_timeout = config.drain_timeout;
let deadline = async move {
loop {
if *trigger_rx.borrow() {
break;
}
if trigger_rx.changed().await.is_err() {
break;
}
}
tokio::time::sleep(drain_timeout).await;
};
tokio::select! {
result = server => {
run_stop_hooks();
run_lifecycle_stop_hooks().await;
result
}
_ = deadline => {
run_stop_hooks();
run_lifecycle_stop_hooks().await;
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_thirty_second_drain() {
assert_eq!(
GracefulShutdownConfig::default().drain_timeout,
Duration::from_secs(30)
);
let custom = GracefulShutdownConfig::with_drain_timeout(Duration::from_millis(250));
assert_eq!(custom.drain_timeout, Duration::from_millis(250));
}
}