diff --git a/apps/myelin/src/orchestration/app.rs b/apps/myelin/src/orchestration/app.rs index 7f5827f..d5da374 100644 --- a/apps/myelin/src/orchestration/app.rs +++ b/apps/myelin/src/orchestration/app.rs @@ -2375,7 +2375,8 @@ fn spawn_stop_listener( } #[cfg(target_os = "linux")] - swactor_process::spawn_os_stop_signal_wait(sender, actor); + swactor_process::spawn_os_stop_signal_wait(sender, actor) + .map_err(|error| format!("install orchestrator stop signal handler: {error}"))?; Ok(()) } diff --git a/crates/process/src/operations.rs b/crates/process/src/operations.rs index 040db23..65a9fe7 100644 --- a/crates/process/src/operations.rs +++ b/crates/process/src/operations.rs @@ -147,19 +147,22 @@ fn spawn_stop_channel_wait_thread( } /// Wait for SIGINT/SIGTERM and notify an actor once. +/// +/// Signal handlers are installed before this function returns. A caller may +/// therefore publish readiness immediately after a successful return without +/// racing the operating system's default signal action. #[cfg(target_os = "linux")] -pub fn spawn_os_stop_signal_wait(sender: ExternalSender, actor: ActorAddress) { +pub fn spawn_os_stop_signal_wait(sender: ExternalSender, actor: ActorAddress) -> io::Result<()> { + let mut signals = signal_hook::iterator::Signals::new([ + signal_hook::consts::signal::SIGINT, + signal_hook::consts::signal::SIGTERM, + ])?; thread::spawn(move || { - let Ok(mut signals) = signal_hook::iterator::Signals::new([ - signal_hook::consts::signal::SIGINT, - signal_hook::consts::signal::SIGTERM, - ]) else { - return; - }; if signals.forever().next().is_some() { let _ = sender.send_to(actor, ProcessStopSignal); } }); + Ok(()) } #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/xtask/src/demo/mod.rs b/xtask/src/demo/mod.rs index 3bb5bda..6788392 100644 --- a/xtask/src/demo/mod.rs +++ b/xtask/src/demo/mod.rs @@ -503,7 +503,8 @@ fn run_supervisor(args: &[String]) -> Result<(), String> { }) .map_err(|error| format!("spawn supervisor stop actor: {error}"))?; #[cfg(target_os = "linux")] - swactor_process::spawn_os_stop_signal_wait(runtime.create_sender(), stop_actor); + swactor_process::spawn_os_stop_signal_wait(runtime.create_sender(), stop_actor) + .map_err(|error| format!("install supervisor stop signal handler: {error}"))?; println!("demo: dashboard on http://localhost:{port}"); println!(" /view/fleet — per-node cards (pid, lifecycle)"); println!(" /view/demo-control — Fleet Control: stages, feeds, kill / provision / edge"); diff --git a/xtask/src/demo/node.rs b/xtask/src/demo/node.rs index b7e0a2f..2734c50 100644 --- a/xtask/src/demo/node.rs +++ b/xtask/src/demo/node.rs @@ -256,7 +256,8 @@ pub fn run_node_role(supervisor_addr_json: &str, attempt: u64) -> Result<(), Str }) .map_err(|error| format!("spawn node stop actor: {error}"))?; #[cfg(target_os = "linux")] - swactor_process::spawn_os_stop_signal_wait(runtime.create_sender(), stop_actor); + swactor_process::spawn_os_stop_signal_wait(runtime.create_sender(), stop_actor) + .map_err(|error| format!("install node stop signal handler: {error}"))?; // The actor owns lifecycle; the entrypoint waits only for its terminal signal. completion.wait();