Skip to content

Commit c206647

Browse files
authored
(fix): Remove double-kill bug in Windows process termination (#777) (#855)
* fix: Remove double-kill bug in Windows process termination (#777) Problems fixed: - Removed double TASKKILL execution when SendInterrupt is enabled - Simplified kill logic to match Linux behavior (single kill command) - Added console window hiding for cleaner UX (no flashing windows) - Switched from PowerShell to cmd.exe for better performance - Improved logging to match Linux output format - Better error handling for already-terminated processes The previous code would run TASKKILL twice when SendInterrupt was enabled, or handle it inconsistently when disabled. This caused processes to not be properly terminated, leading to port conflicts and orphaned processes as reported in #777. Testing on Linux shows clean process transitions with no port conflicts. Windows users requested to test and verify the fix. Fixes #777 * refactor: Address review feedback - match Linux logging style - Use mainDebug instead of runnerLog to match Linux implementation - Remove verbose logging as requested by maintainer - Clarify why send_interrupt is not supported on Windows - Simplify error handling to match Linux style - Keep core fix: single TASKKILL, console hiding, proper Wait() Addresses feedback from @xiantang in PR review.
1 parent 7c0d51f commit c206647

File tree

1 file changed

+41
-16
lines changed

1 file changed

+41
-16
lines changed

runner/util_windows.go

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,78 @@
1+
//go:build windows
2+
13
package runner
24

35
import (
6+
"fmt"
47
"io"
58
"os"
69
"os/exec"
710
"strconv"
811
"strings"
9-
"time"
12+
"syscall"
1013
)
1114

1215
func (e *Engine) killCmd(cmd *exec.Cmd) (pid int, err error) {
1316
pid = cmd.Process.Pid
14-
// https://stackoverflow.com/a/44551450
15-
kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(pid))
1617

18+
// On Windows, SIGINT is not supported for process trees.
19+
// Windows uses different process termination mechanisms than Unix.
20+
// TASKKILL is the proper way to terminate process hierarchies on Windows.
1721
if e.config.Build.SendInterrupt {
18-
if err = kill.Run(); err != nil {
19-
return
20-
}
21-
time.Sleep(e.config.killDelay())
22+
e.mainLog("send_interrupt is not supported on Windows, using TASKKILL instead")
23+
}
24+
25+
// Use TASKKILL to forcefully terminate the entire process tree
26+
e.mainDebug("sending TASKKILL to process tree")
27+
killCmd := exec.Command("TASKKILL", "/F", "/T", "/PID", strconv.Itoa(pid))
28+
29+
// Hide the console window for cleaner UX
30+
killCmd.SysProcAttr = &syscall.SysProcAttr{
31+
HideWindow: true,
32+
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
2233
}
23-
err = kill.Run()
24-
// Wait releases any resources associated with the Process.
34+
35+
err = killCmd.Run()
36+
37+
// Wait for the process to fully terminate and release resources
2538
_, _ = cmd.Process.Wait()
39+
2640
return pid, err
2741
}
2842

2943
func (e *Engine) startCmd(cmd string) (*exec.Cmd, io.ReadCloser, io.ReadCloser, error) {
3044
var err error
3145

32-
if !strings.Contains(cmd, ".exe") {
33-
e.runnerLog("CMD will not recognize non .exe file for execution, path: %s", cmd)
46+
if !strings.Contains(cmd, ".exe") && !strings.Contains(cmd, ".bat") && !strings.Contains(cmd, ".cmd") {
47+
e.mainDebug("command may not be recognized as executable: %s", cmd)
3448
}
35-
c := exec.Command("powershell", cmd)
49+
50+
// Use cmd.exe instead of PowerShell for better performance
51+
c := exec.Command("cmd", "/C", cmd)
52+
53+
// Hide the console window
54+
c.SysProcAttr = &syscall.SysProcAttr{
55+
HideWindow: true,
56+
CreationFlags: 0x08000000, // CREATE_NO_WINDOW
57+
}
58+
3659
stderr, err := c.StderrPipe()
3760
if err != nil {
38-
return nil, nil, nil, err
61+
return nil, nil, nil, fmt.Errorf("failed to create stderr pipe: %w", err)
3962
}
63+
4064
stdout, err := c.StdoutPipe()
4165
if err != nil {
42-
return nil, nil, nil, err
66+
return nil, nil, nil, fmt.Errorf("failed to create stdout pipe: %w", err)
4367
}
4468

4569
c.Stdout = os.Stdout
4670
c.Stderr = os.Stderr
4771

4872
err = c.Start()
4973
if err != nil {
50-
return nil, nil, nil, err
74+
return nil, nil, nil, fmt.Errorf("failed to start command: %w", err)
5175
}
52-
return c, stdout, stderr, err
76+
77+
return c, stdout, stderr, nil
5378
}

0 commit comments

Comments
 (0)