diff --git a/internal/daemon/discovery.go b/internal/daemon/discovery.go index 45350c47..1344c395 100644 --- a/internal/daemon/discovery.go +++ b/internal/daemon/discovery.go @@ -411,9 +411,9 @@ func stopDaemonWithTimeout(daemon DaemonInfo) error { } } - // Try SIGTERM with 3 second timeout + // Try graceful kill with 3 second timeout if err := killProcess(daemon.PID); err != nil { - return fmt.Errorf("SIGTERM failed: %w", err) + return fmt.Errorf("kill process failed: %w", err) } // Wait up to 3 seconds for process to die @@ -424,9 +424,9 @@ func stopDaemonWithTimeout(daemon DaemonInfo) error { } } - // SIGTERM timeout, try SIGKILL with 1 second timeout + // Graceful kill timeout, try force kill with 1 second timeout if err := forceKillProcess(daemon.PID); err != nil { - return fmt.Errorf("SIGKILL failed: %w", err) + return fmt.Errorf("force kill failed: %w", err) } // Wait up to 1 second for process to die @@ -437,5 +437,5 @@ func stopDaemonWithTimeout(daemon DaemonInfo) error { } } - return fmt.Errorf("process %d did not die after SIGKILL", daemon.PID) + return fmt.Errorf("process %d did not die after force kill", daemon.PID) } diff --git a/internal/daemon/kill_windows.go b/internal/daemon/kill_windows.go index f57b848d..411c2f3b 100644 --- a/internal/daemon/kill_windows.go +++ b/internal/daemon/kill_windows.go @@ -3,45 +3,70 @@ package daemon import ( - "bytes" "fmt" + "os" "os/exec" - "strconv" ) func killProcess(pid int) error { - // Use taskkill on Windows (without /F for graceful) - cmd := exec.Command("taskkill", "/PID", strconv.Itoa(pid)) - if err := cmd.Run(); err != nil { + // On Windows, there's no SIGTERM equivalent for console processes. + // The graceful RPC shutdown is already attempted before this function is called. + // Use os.Process.Kill() which calls TerminateProcess - the reliable Windows API. + proc, err := os.FindProcess(pid) + if err != nil { + return fmt.Errorf("failed to find process %d: %w", pid, err) + } + if err := proc.Kill(); err != nil { return fmt.Errorf("failed to kill PID %d: %w", pid, err) } return nil } func forceKillProcess(pid int) error { - // Use taskkill with /F flag for force kill - cmd := exec.Command("taskkill", "/PID", strconv.Itoa(pid), "/F") - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to force kill PID %d: %w", pid, err) - } - return nil + // On Windows, Kill() already uses TerminateProcess which is forceful + return killProcess(pid) } func isProcessAlive(pid int) bool { - // Use tasklist to check if process exists + // On Windows, FindProcess always succeeds, but we can check if the process + // is actually running by trying to get its exit code via Wait with WNOHANG. + // A simpler approach: try to open the process and check for errors. + proc, err := os.FindProcess(pid) + if err != nil { + return false + } + // On Windows, we need to actually check if the process exists. + // Signal(nil) on Windows returns an error if process doesn't exist. + // However, os.Process.Signal is not implemented on Windows. + // Use a different approach: try to kill with signal 0 equivalent. + // Actually, on Windows we can check via process handle opening. + // The simplest reliable way is to use tasklist. + // + // Note: os.FindProcess on Windows always succeeds regardless of whether + // the process exists. We need to actually try to interact with it. + // Using Release() and checking the error doesn't work either. + // + // Fall back to tasklist for reliability. + return isProcessAliveTasklist(pid, proc) +} + +func isProcessAliveTasklist(pid int, _ *os.Process) bool { + // Use Windows API via tasklist to check if process exists + // This is the most reliable method on Windows cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid), "/NH") output, err := cmd.Output() if err != nil { return false } - // Check if output contains the PID - return contains(string(output), strconv.Itoa(pid)) + // Check if output contains the PID (tasklist returns "INFO: No tasks..." if not found) + return containsSubstring(string(output), fmt.Sprintf("%d", pid)) } -func contains(s, substr string) bool { - return findSubstring(s, substr) -} - -func findSubstring(s, substr string) bool { - return len(s) >= len(substr) && bytes.Contains([]byte(s), []byte(substr)) +func containsSubstring(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false }