Files
Vintner/internal/wrapper/signals_test.go
T
Cheviiot 98ee39018a Stop orphaning wine subprocesses when vintner is killed by PID
Every wrapped tool invocation (cl/link/msbuild/etc via wine, plus the
native cmd/findstr shims) now starts its child in its own process
group and forwards SIGINT/SIGTERM to that group, escalating to
SIGKILL after a 5s grace period if it doesn't exit.

Previously, interactive Ctrl-C happened to work by accident (the
child inherited the terminal's foreground process group and got the
signal directly), but anything that signals vintner by PID alone - a
CI job's timeout, a supervisor's `kill <pid>` - never reached the
wine/wineserver tree underneath it, which got reparented to init and
kept running: wasted CPU, held file locks, stray FIFOs/temp files.

Verified two ways: a unit test (signals_test.go) that starts a
detached `sleep 30`, signals the test process itself, and checks the
child actually dies; and a real end-to-end run - killed an in-flight
`msbuild` driver build by PID mid-compile and confirmed no orphaned
msbuild/cl/link/vintner process was left behind (wineserver and its
persistent service processes are expected to survive, by design - see
pipeDrainGrace's doc comment).
2026-07-25 10:34:45 +10:00

44 lines
1.2 KiB
Go

package wrapper
import (
"os"
"os/exec"
"syscall"
"testing"
"time"
)
// TestForwardSignalsKillsChild verifies the actual mechanism that keeps a
// wine subprocess from being orphaned: a SIGTERM delivered to the current
// process (mimicking `kill <vintner-pid>`, not an interactive Ctrl-C) must
// reach a child started with setNewProcessGroup, even though it's no longer
// in the same process group.
func TestForwardSignalsKillsChild(t *testing.T) {
cmd := exec.Command("sleep", "30")
setNewProcessGroup(cmd)
if err := cmd.Start(); err != nil {
t.Fatalf("starting sleep: %v", err)
}
stop := forwardSignals(cmd.Process)
defer stop()
// signal.Notify (inside forwardSignals) intercepts this rather than
// letting it terminate the test binary itself.
if err := syscall.Kill(os.Getpid(), syscall.SIGTERM); err != nil {
t.Fatalf("signaling self: %v", err)
}
done := make(chan error, 1)
go func() { done <- cmd.Wait() }()
select {
case err := <-done:
if err == nil {
t.Fatal("expected the child to be killed by the forwarded signal, but it exited successfully")
}
case <-time.After(3 * time.Second):
cmd.Process.Kill()
t.Fatal("child was still running 3s after the signal should have been forwarded")
}
}