killProcessTree kills a process and all its children on Windows
(pid int)
| 115 | |
| 116 | // killProcessTree kills a process and all its children on Windows |
| 117 | func killProcessTree(pid int) error { |
| 118 | proc, err := os.FindProcess(pid) |
| 119 | if err != nil { |
| 120 | // Process might not exist, check if it's running |
| 121 | if !isProcessRunning(pid) { |
| 122 | return nil |
| 123 | } |
| 124 | return fmt.Errorf("failed to find process %d: %w", pid, err) |
| 125 | } |
| 126 | |
| 127 | // Try graceful termination first (CTRL_BREAK_EVENT for process groups) |
| 128 | // For process groups created with CREATE_NEW_PROCESS_GROUP, we need to use |
| 129 | // GenerateConsoleCtrlEvent to send signals to the group |
| 130 | _ = proc.Signal(syscall.SIGTERM) |
| 131 | |
| 132 | // Use taskkill with /T flag to kill process tree (parent and all children) |
| 133 | // /F forces termination, /T kills child processes, /PID specifies the process |
| 134 | cmd := exec.Command("taskkill", "/F", "/T", "/PID", strconv.Itoa(pid)) |
| 135 | output, err := cmd.CombinedOutput() |
| 136 | if err != nil { |
| 137 | // Check if process is already dead (taskkill returns error if process not found) |
| 138 | if !isProcessRunning(pid) { |
| 139 | return nil |
| 140 | } |
| 141 | // Check if error is because process doesn't exist |
| 142 | outputStr := strings.ToLower(string(output)) |
| 143 | if strings.Contains(outputStr, "not found") || strings.Contains(outputStr, "does not exist") { |
| 144 | return nil |
| 145 | } |
| 146 | return fmt.Errorf("failed to kill process tree %d: %w (output: %s)", pid, err, string(output)) |
| 147 | } |
| 148 | |
| 149 | // Give it a moment to terminate |
| 150 | // Verify it's actually dead |
| 151 | if !isProcessRunning(pid) { |
| 152 | return nil |
| 153 | } |
| 154 | |
| 155 | // If still running, try one more time with more aggressive approach |
| 156 | // This shouldn't normally happen, but just in case |
| 157 | time.Sleep(100 * time.Millisecond) |
| 158 | if !isProcessRunning(pid) { |
| 159 | return nil |
| 160 | } |
| 161 | |
| 162 | return fmt.Errorf("process %d is still running after kill attempt", pid) |
| 163 | } |
| 164 | |
| 165 | // verifyProcessDead checks if a process is actually dead |
| 166 | func verifyProcessDead(pid int) error { |
nothing calls this directly
no test coverage detected