fix(daemon): handle sigterm shutdown signal
Wait for either SIGINT or SIGTERM on Unix so daemon mode behaves correctly under container and process-manager termination flows. Record signal-specific shutdown reasons and add unit tests for shutdown signal labeling. Refs #2529
This commit is contained in:
parent
02cf1a558a
commit
7bdf8eb609
@ -8,6 +8,40 @@ use tokio::time::Duration;
|
||||
|
||||
const STATUS_FLUSH_SECONDS: u64 = 5;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum ShutdownSignal {
|
||||
CtrlC,
|
||||
SigTerm,
|
||||
}
|
||||
|
||||
fn shutdown_reason(signal: ShutdownSignal) -> &'static str {
|
||||
match signal {
|
||||
ShutdownSignal::CtrlC => "shutdown requested (SIGINT)",
|
||||
ShutdownSignal::SigTerm => "shutdown requested (SIGTERM)",
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_shutdown_signal() -> Result<ShutdownSignal> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
|
||||
let mut sigterm = signal(SignalKind::terminate())?;
|
||||
tokio::select! {
|
||||
ctrl_c = tokio::signal::ctrl_c() => {
|
||||
ctrl_c?;
|
||||
Ok(ShutdownSignal::CtrlC)
|
||||
}
|
||||
_ = sigterm.recv() => Ok(ShutdownSignal::SigTerm),
|
||||
}
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
tokio::signal::ctrl_c().await?;
|
||||
Ok(ShutdownSignal::CtrlC)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(config: Config, host: String, port: u16) -> Result<()> {
|
||||
// Pre-flight: check if port is already in use by another zeroclaw daemon
|
||||
if let Err(_e) = check_port_available(&host, port).await {
|
||||
@ -106,10 +140,10 @@ pub async fn run(config: Config, host: String, port: u16) -> Result<()> {
|
||||
println!("🧠 ZeroClaw daemon started");
|
||||
println!(" Gateway: http://{host}:{port}");
|
||||
println!(" Components: gateway, channels, heartbeat, scheduler");
|
||||
println!(" Ctrl+C to stop");
|
||||
println!(" Ctrl+C or SIGTERM to stop");
|
||||
|
||||
tokio::signal::ctrl_c().await?;
|
||||
crate::health::mark_component_error("daemon", "shutdown requested");
|
||||
let signal = wait_for_shutdown_signal().await?;
|
||||
crate::health::mark_component_error("daemon", shutdown_reason(signal));
|
||||
|
||||
for handle in &handles {
|
||||
handle.abort();
|
||||
@ -444,6 +478,22 @@ mod tests {
|
||||
assert_eq!(path, tmp.path().join("daemon_state.json"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shutdown_reason_for_ctrl_c_mentions_sigint() {
|
||||
assert_eq!(
|
||||
shutdown_reason(ShutdownSignal::CtrlC),
|
||||
"shutdown requested (SIGINT)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shutdown_reason_for_sigterm_mentions_sigterm() {
|
||||
assert_eq!(
|
||||
shutdown_reason(ShutdownSignal::SigTerm),
|
||||
"shutdown requested (SIGTERM)"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn supervisor_marks_error_and_restart_on_failure() {
|
||||
let handle = spawn_component_supervisor("daemon-test-fail", 1, 1, || async {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user