From c4f50600eb98bfd495d7d125be929852736c005c Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Fri, 19 Jun 2026 15:25:12 -0600 Subject: [PATCH] Don't panic when a finished compile has neither code nor signal get_signal did `status.signal().expect("must have signal")`, assuming the Unix invariant that an ExitStatus with no exit code was terminated by a signal. That does not always hold: an ExitStatus reconstructed for a distributed compile (or an abnormal wait status such as WIFSTOPPED) can report neither a code nor a signal. When that happened the expect() panicked the compile task, which the server surfaced as a misleading "Failed to bind socket" and, under load, repeatedly fell back to local compilation. Return Option from get_signal and assign it straight into res.signal, so a compile that reports neither code nor signal leaves res.signal unset instead of crashing the in-flight task. The Windows arm returns None rather than panicking; ExitStatus::code() is always Some there, so the signal branch is never reached anyway. Add a unit test covering a real terminating signal (SIGKILL) and the neither-code-nor-signal case (WIFSTOPPED via from_raw), which previously panicked. Signed-off-by: Javier Tia --- src/server.rs | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/server.rs b/src/server.rs index 98cedfbbc..25ba43a9a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -130,13 +130,20 @@ fn notify_server_startup(name: Option<&OsString>, status: ServerStartup) -> Resu } #[cfg(unix)] -fn get_signal(status: ExitStatus) -> i32 { +fn get_signal(status: ExitStatus) -> Option { use std::os::unix::prelude::*; - status.signal().expect("must have signal") + // None when the process produced neither an exit code nor a terminating + // signal - e.g. an ExitStatus synthesized from a distributed-compile result, + // or an abnormal wait status. Previously this was `.expect("must have + // signal")`, which panicked the compile task (surfacing as a misleading + // "Failed to bind socket") and could be tripped repeatedly under load. + status.signal() } #[cfg(windows)] -fn get_signal(_status: ExitStatus) -> i32 { - panic!("no signals on windows") +fn get_signal(_status: ExitStatus) -> Option { + // On Windows ExitStatus::code() is always Some, so the signal branch is + // never reached; return None rather than panicking. + None } pub struct DistClientContainer { @@ -1461,7 +1468,7 @@ where match status.code() { Some(code) => res.retcode = Some(code), - None => res.signal = Some(get_signal(status)), + None => res.signal = get_signal(status), } res.stdout = stdout; @@ -1477,7 +1484,7 @@ where match output.status.code() { Some(code) => res.retcode = Some(code), - None => res.signal = Some(get_signal(output.status)), + None => res.signal = get_signal(output.status), } res.stdout = output.stdout; res.stderr = output.stderr; @@ -2282,6 +2289,26 @@ fn waits_until_zero() { mod tests { use super::*; + #[cfg(unix)] + #[test] + fn test_get_signal_handles_signal_and_abnormal_status() { + use std::os::unix::process::ExitStatusExt; + + // Terminated by SIGKILL: a real terminating signal is reported. + let killed = ExitStatus::from_raw(9); + assert_eq!(killed.code(), None); + assert_eq!(get_signal(killed), Some(9)); + + // A wait status that is neither a normal exit (code) nor a terminating + // signal - here WIFSTOPPED (low byte 0x7f). The same neither-code-nor- + // signal shape can arise from an ExitStatus synthesized for a + // distributed compile. get_signal must return None, not panic + // "must have signal" (which used to crash the in-flight compile task). + let abnormal = ExitStatus::from_raw(0x7f); + assert_eq!(abnormal.code(), None); + assert_eq!(get_signal(abnormal), None); + } + struct StringWriter { buffer: String, }