soma_http_server/shutdown.rs
1//! Graceful shutdown signal.
2//!
3//! Resolves on `Ctrl+C` or, on Unix, `SIGTERM` — the two signals a process
4//! manager (systemd, Docker, Kubernetes) or an interactive terminal sends to
5//! ask a server to stop accepting new work and drain in-flight requests.
6//!
7//! Known limitation: if registering a signal handler itself fails (e.g. a
8//! sandboxed/seccomp-restricted environment that disallows the underlying
9//! syscalls), that branch logs an `error` and then never resolves — it does
10//! not abort the process, it just stops competing in the `tokio::select!`
11//! below so the *other* signal can still trigger shutdown. If both
12//! registrations fail, this future never resolves at all and the only way
13//! to stop the process becomes an uncatchable `SIGKILL` (no request
14//! draining). That failure mode is easy to miss: the only evidence is a
15//! startup-time log line, well before the eventual shutdown attempt that
16//! silently doesn't work. Since SIGTERM is the primary signal process
17//! managers actually send, a failed SIGTERM registration in particular
18//! means production `docker stop`/`kubectl delete pod` shutdowns hard-kill
19//! instead of draining, not just Ctrl+C in a local terminal.
20pub async fn shutdown_signal() {
21 let ctrl_c = async {
22 if let Err(error) = tokio::signal::ctrl_c().await {
23 tracing::error!(%error, "CTRL+C handler failed");
24 std::future::pending::<()>().await;
25 }
26 };
27
28 #[cfg(unix)]
29 let terminate = async {
30 match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
31 Ok(mut signal) => {
32 signal.recv().await;
33 }
34 Err(error) => {
35 tracing::error!(%error, "SIGTERM handler failed");
36 std::future::pending::<()>().await;
37 }
38 }
39 };
40
41 #[cfg(not(unix))]
42 let terminate = std::future::pending::<()>();
43
44 tokio::select! {
45 () = ctrl_c => {}
46 () = terminate => {}
47 }
48 tracing::info!("shutdown signal received");
49}