feat(server): graceful shutdown on SIGINT/SIGTERM (#1)

This commit is contained in:
2026-06-04 21:42:55 +02:00
parent b0d2c247df
commit 7e235ffd3e
+29
View File
@@ -64,6 +64,34 @@ pub async fn run(config: Config) -> anyhow::Result<()> {
serve(listener, state).await serve(listener, state).await
} }
/// Resolves when the process receives SIGINT (Ctrl-C) or SIGTERM, so the server can
/// drain in-flight requests before exiting.
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("install Ctrl-C handler");
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("install SIGTERM handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
tracing::info!("shutdown signal received; draining");
}
/// Serve the API on an already-bound listener (used by `run` and tests). /// Serve the API on an already-bound listener (used by `run` and tests).
pub async fn serve(listener: TcpListener, state: AppState) -> anyhow::Result<()> { pub async fn serve(listener: TcpListener, state: AppState) -> anyhow::Result<()> {
let app = build_app(state); let app = build_app(state);
@@ -72,6 +100,7 @@ pub async fn serve(listener: TcpListener, state: AppState) -> anyhow::Result<()>
let app = app.merge(web_assets::routes()); let app = app.merge(web_assets::routes());
axum::serve(listener, app) axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await .await
.context("running the HTTP server")?; .context("running the HTTP server")?;