From c2df5d786d5b7944690c169dd57dba75b8caf352 Mon Sep 17 00:00:00 2001 From: Bohan Jiang <52446949+Bohan-J@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:15:41 +0800 Subject: [PATCH] test(execenv): fix flaky Windows prepare-helper deadlock (MUL-4923) (#5676) The windows-execenv job's TestPrepareIsolated_WindowsKillsDescendantBeforeRetry flakes on the Windows runner: its helper subprocess ends in a bare select{}, which the Go runtime can reap with 'all goroutines are asleep - deadlock!' (exit status 2) once every goroutine is parked with no wakeup source. That races the parent's Job Object kill, so PrepareIsolated returns 'helper failed' instead of the context.Canceled the test asserts. Block on a timer-backed sleep loop instead: a pending timer is a wakeable source, so the runtime never declares a deadlock, and the process still dies the instant the Job Object tears the tree down. Co-authored-by: Bohan-J Co-authored-by: multica-agent --- .../daemon/execenv/isolation_windows_test.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/server/internal/daemon/execenv/isolation_windows_test.go b/server/internal/daemon/execenv/isolation_windows_test.go index f3396816a..56f5b4192 100644 --- a/server/internal/daemon/execenv/isolation_windows_test.go +++ b/server/internal/daemon/execenv/isolation_windows_test.go @@ -53,7 +53,18 @@ func TestWindowsPreparationJobHelperProcess(t *testing.T) { if err := os.WriteFile(ready, []byte("ready"), 0o600); err != nil { os.Exit(4) } - select {} + // Block until the parent tears this whole process tree down via the Job + // Object. A bare select{} is unsafe here: once the helper reaches it, the Go + // runtime can find every goroutine parked with no possible wakeup and reap + // the process itself with "all goroutines are asleep - deadlock!" (exit + // status 2). That races the parent's kill and surfaces as a spurious + // "helper failed" instead of the context.Canceled the parent test asserts, + // which is the flake this job hit on main. Sleeping keeps a pending timer — + // a wakeable source — so the runtime never declares a deadlock; the process + // still dies the instant the Job Object kills the tree. + for { + time.Sleep(time.Hour) + } } func TestWindowsPreparationDelayedWriter(t *testing.T) {