From a5f844d7cc242406df180f7ebea28e136fb61080 Mon Sep 17 00:00:00 2001 From: Argenis Date: Mon, 16 Mar 2026 14:21:59 -0400 Subject: [PATCH] fix(daemon): ignore SIGHUP to survive terminal/SSH disconnect (#3721) * fix(daemon): ignore SIGHUP to survive terminal/SSH disconnect (#3688) * style: remove redundant continue flagged by clippy --- src/daemon/mod.rs | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index eee231220..a3144d56b 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -8,7 +8,8 @@ use tokio::time::Duration; const STATUS_FLUSH_SECONDS: u64 = 5; -/// Wait for shutdown signal (SIGINT or SIGTERM) +/// Wait for shutdown signal (SIGINT or SIGTERM). +/// SIGHUP is explicitly ignored so the daemon survives terminal/SSH disconnects. async fn wait_for_shutdown_signal() -> Result<()> { #[cfg(unix)] { @@ -16,13 +17,21 @@ async fn wait_for_shutdown_signal() -> Result<()> { let mut sigint = signal(SignalKind::interrupt())?; let mut sigterm = signal(SignalKind::terminate())?; + let mut sighup = signal(SignalKind::hangup())?; - tokio::select! { - _ = sigint.recv() => { - tracing::info!("Received SIGINT, shutting down..."); - } - _ = sigterm.recv() => { - tracing::info!("Received SIGTERM, shutting down..."); + loop { + tokio::select! { + _ = sigint.recv() => { + tracing::info!("Received SIGINT, shutting down..."); + break; + } + _ = sigterm.recv() => { + tracing::info!("Received SIGTERM, shutting down..."); + break; + } + _ = sighup.recv() => { + tracing::info!("Received SIGHUP, ignoring (daemon stays running)"); + } } } } @@ -774,4 +783,28 @@ mod tests { let target = auto_detect_heartbeat_channel(&config); assert!(target.is_none()); } + + /// Verify that SIGHUP does not cause shutdown — the daemon should ignore it + /// and only terminate on SIGINT or SIGTERM. + #[cfg(unix)] + #[tokio::test] + async fn sighup_does_not_shut_down_daemon() { + use libc; + use tokio::time::{timeout, Duration}; + + let handle = tokio::spawn(wait_for_shutdown_signal()); + + // Give the signal handler time to register + tokio::time::sleep(Duration::from_millis(50)).await; + + // Send SIGHUP to ourselves — should be ignored by the handler + unsafe { libc::raise(libc::SIGHUP) }; + + // The future should NOT complete within a short window + let result = timeout(Duration::from_millis(200), handle).await; + assert!( + result.is_err(), + "wait_for_shutdown_signal should not return after SIGHUP" + ); + } }