InterruptProcessTree interrupts an entire process tree using the given signal
(logger *zap.Logger, ppid int, sig syscall.Signal)
| 831 | |
| 832 | // InterruptProcessTree interrupts an entire process tree using the given signal |
| 833 | func InterruptProcessTree(logger *zap.Logger, ppid int, sig syscall.Signal) error { |
| 834 | // Windows: /proc and process group semantics don't exist. Prefer console ctrl events, fallback to taskkill. |
| 835 | if runtime.GOOS == "windows" { |
| 836 | logger.Debug("Interrupting process tree on windows", zap.Int("pid", ppid), zap.String("signal", sig.String())) |
| 837 | |
| 838 | // List ALL descendant processes (recursively) before attempting to kill |
| 839 | listAllCmd := exec.Command("powershell", "-Command", |
| 840 | fmt.Sprintf(`$parent = %d |
| 841 | $processes = Get-CimInstance Win32_Process |
| 842 | function Get-ChildProcesses($parentId) { |
| 843 | $children = $processes | Where-Object {$_.ParentProcessId -eq $parentId} |
| 844 | foreach ($child in $children) { |
| 845 | $child |
| 846 | Get-ChildProcesses $child.ProcessId |
| 847 | } |
| 848 | } |
| 849 | Get-ChildProcesses $parent | Select-Object ProcessId, ParentProcessId, Name, CommandLine | Format-List`, ppid)) |
| 850 | if output, err := listAllCmd.CombinedOutput(); err == nil { |
| 851 | logger.Debug("ALL descendant processes before kill", zap.Int("parent_pid", ppid), zap.String("output", string(output))) |
| 852 | } else { |
| 853 | logger.Debug("Failed to list descendant processes", zap.Int("pid", ppid), zap.Error(err), zap.String("output", string(output))) |
| 854 | } |
| 855 | |
| 856 | // Prefer a console Ctrl+Break event so the app can exit cleanly. |
| 857 | if sig == syscall.SIGINT { |
| 858 | if err := SendSignal(logger, -ppid, sig); err == nil { |
| 859 | if err := waitForProcessExit(ppid, 10*time.Second, logger); err != nil { |
| 860 | logger.Error("error waiting for process to exit", zap.Int("pid", ppid), zap.Error(err)) |
| 861 | } |
| 862 | if running, err := isProcessRunning(ppid); err == nil && !running { |
| 863 | return nil |
| 864 | } |
| 865 | } else { |
| 866 | logger.Debug("failed to send console ctrl event", zap.Int("pid", ppid), zap.Error(err)) |
| 867 | } |
| 868 | } |
| 869 | |
| 870 | // Try graceful taskkill as a fallback (without /F). |
| 871 | cmd := exec.Command("taskkill", "/PID", strconv.Itoa(ppid), "/T") |
| 872 | if output, err := cmd.CombinedOutput(); err != nil { |
| 873 | logger.Debug("taskkill graceful failed", zap.Int("pid", ppid), zap.Error(err), zap.String("output", string(output))) |
| 874 | } else { |
| 875 | logger.Debug("taskkill graceful succeeded", zap.Int("pid", ppid), zap.String("output", string(output))) |
| 876 | } |
| 877 | |
| 878 | // Force kill as the last resort if the process tree is still alive. |
| 879 | if running, err := isProcessRunning(ppid); err != nil { |
| 880 | logger.Debug("failed to check process state after graceful taskkill", zap.Int("pid", ppid), zap.Error(err)) |
| 881 | } else if running { |
| 882 | forcedCmd := exec.Command("taskkill", "/PID", strconv.Itoa(ppid), "/T", "/F") |
| 883 | if output, err := forcedCmd.CombinedOutput(); err != nil { |
| 884 | logger.Error("taskkill forced failed", zap.Int("pid", ppid), zap.Error(err), zap.String("output", string(output))) |
| 885 | } else { |
| 886 | logger.Debug("taskkill forced succeeded", zap.Int("pid", ppid), zap.String("output", string(output))) |
| 887 | } |
| 888 | } |
| 889 | |
| 890 | // Check for remaining descendant processes after kill |
no test coverage detected