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 <bohan@devv.ai>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Bohan Jiang
2026-07-20 16:15:41 +08:00
committed by GitHub
parent d3fac023c9
commit c2df5d786d

View File

@@ -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) {