use std::time::Duration;
use tokio::signal;
const DEFAULT_SHUTDOWN_TIMEOUT_SECS: u64 = 30;
pub async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install SIGTERM handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {
tracing::info!("received Ctrl+C, initiating graceful shutdown");
}
_ = terminate => {
tracing::info!("received SIGTERM, initiating graceful shutdown");
}
}
}
pub async fn shutdown_cleanup(db: &sqlx::SqlitePool, timeout: Option<Duration>) {
let timeout = timeout.unwrap_or(Duration::from_secs(DEFAULT_SHUTDOWN_TIMEOUT_SECS));
tracing::info!(timeout_secs = timeout.as_secs(), "running shutdown cleanup");
let cleanup = async {
tracing::debug!("closing database connection pool");
db.close().await;
tracing::debug!("database pool closed");
};
match tokio::time::timeout(timeout, cleanup).await {
Ok(()) => tracing::info!("shutdown cleanup completed"),
Err(_) => tracing::warn!("shutdown cleanup timed out"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_timeout_is_30_seconds() {
assert_eq!(DEFAULT_SHUTDOWN_TIMEOUT_SECS, 30);
}
#[tokio::test]
async fn shutdown_cleanup_completes_with_fresh_pool() {
let pool = sqlx::SqlitePool::connect("sqlite::memory:").await.unwrap();
shutdown_cleanup(&pool, Some(Duration::from_secs(5))).await;
assert!(pool.is_closed());
}
#[tokio::test]
async fn shutdown_cleanup_handles_already_closed_pool() {
let pool = sqlx::SqlitePool::connect("sqlite::memory:").await.unwrap();
pool.close().await;
shutdown_cleanup(&pool, Some(Duration::from_secs(1))).await;
}
}