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).
This commit is contained in:
Cheviiot
2026-07-25 10:34:45 +10:00
parent 4b3aacfdf7
commit 98ee39018a
4 changed files with 127 additions and 1 deletions
+9
View File
@@ -91,6 +91,7 @@ func Run(tool string, args []string) int {
}
cmd.Env = env
cmd.Stdin = os.Stdin
setNewProcessGroup(cmd)
exitCode = runRawStdout(cmd)
default:
relay := filepath.Join(paths.BaseUnix, "bin", toolRelayName)
@@ -100,6 +101,7 @@ func Run(tool string, args []string) int {
cmd := exec.Command(wineBin, append([]string{toolExePath}, rewritten...)...)
cmd.Env = buildEnv(paths)
cmd.Stdin = os.Stdin
setNewProcessGroup(cmd)
exitCode = runFiltered(cmd, s.stdoutFilter, s.stderrFilter)
}
}
@@ -137,6 +139,7 @@ func runViaToolRelay(wineBin, relayExe, exePath string, args []string, paths *wi
cmdArgs := append([]string{relayExe, exePath}, args...)
cmd := exec.Command(wineBin, cmdArgs...)
cmd.Env = append(buildEnv(paths), "MSVCGOWINE_STDOUT="+stdoutFifo, "MSVCGOWINE_STDERR="+stderrFifo)
setNewProcessGroup(cmd)
if devNull, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0); err == nil {
defer devNull.Close()
cmd.Stdout = devNull
@@ -147,6 +150,8 @@ func runViaToolRelay(wineBin, relayExe, exePath string, args []string, paths *wi
fmt.Fprintln(os.Stderr, "vintner:", err)
return 1
}
stopSignals := forwardSignals(cmd.Process)
defer stopSignals()
var wg sync.WaitGroup
wg.Add(2)
@@ -204,6 +209,8 @@ func runRawStdout(cmd *exec.Cmd) int {
fmt.Fprintln(os.Stderr, "vintner:", err)
return 1
}
stopSignals := forwardSignals(cmd.Process)
defer stopSignals()
doneOut := make(chan struct{})
doneErr := make(chan struct{})
@@ -278,6 +285,8 @@ func runFiltered(cmd *exec.Cmd, stdoutF, stderrF lineFilter) int {
fmt.Fprintln(os.Stderr, "vintner:", err)
return 1
}
stopSignals := forwardSignals(cmd.Process)
defer stopSignals()
doneOut := make(chan struct{})
doneErr := make(chan struct{})