diff --git a/internal/refinery/manager.go b/internal/refinery/manager.go index 3fd2cc8f..11a0fd7c 100644 --- a/internal/refinery/manager.go +++ b/internal/refinery/manager.go @@ -113,7 +113,7 @@ func (m *Manager) Start(foreground bool) error { if foreground { // In foreground mode, we're likely running inside the tmux session // that background mode created. Only check PID to avoid self-detection. - if ref.State == StateRunning && ref.PID > 0 && processExists(ref.PID) { + if ref.State == StateRunning && ref.PID > 0 && util.ProcessExists(ref.PID) { return ErrAlreadyRunning } @@ -138,7 +138,7 @@ func (m *Manager) Start(foreground bool) error { } // Also check via PID for backwards compatibility - if ref.State == StateRunning && ref.PID > 0 && processExists(ref.PID) { + if ref.State == StateRunning && ref.PID > 0 && util.ProcessExists(ref.PID) { return ErrAlreadyRunning } @@ -224,7 +224,7 @@ func (m *Manager) Stop() error { } // If we have a PID and it's a different process, try to stop it gracefully - if ref.PID > 0 && ref.PID != os.Getpid() && processExists(ref.PID) { + if ref.PID > 0 && ref.PID != os.Getpid() && util.ProcessExists(ref.PID) { // Send SIGTERM (best-effort graceful stop) if proc, err := os.FindProcess(ref.PID); err == nil { _ = proc.Signal(os.Interrupt) @@ -638,16 +638,6 @@ func (m *Manager) pushWithRetry(targetBranch string, config MergeConfig) error { return fmt.Errorf("push failed after %d retries: %v", config.PushRetryCount, lastErr) } -// processExists checks if a process with the given PID exists. -func processExists(pid int) bool { - proc, err := os.FindProcess(pid) - if err != nil { - return false - } - // On Unix, FindProcess always succeeds; signal 0 tests existence - err = proc.Signal(nil) - return err == nil -} // formatAge formats a duration since the given time. func formatAge(t time.Time) string { diff --git a/internal/util/process.go b/internal/util/process.go new file mode 100644 index 00000000..d175d298 --- /dev/null +++ b/internal/util/process.go @@ -0,0 +1,13 @@ +package util + +import "os" + +// ProcessExists checks if a process with the given PID exists. +// It uses the Unix convention of sending signal 0 to test for process existence. +func ProcessExists(pid int) bool { + proc, err := os.FindProcess(pid) + if err != nil { + return false + } + return proc.Signal(nil) == nil +} diff --git a/internal/util/process_test.go b/internal/util/process_test.go new file mode 100644 index 00000000..1618d1d0 --- /dev/null +++ b/internal/util/process_test.go @@ -0,0 +1,25 @@ +package util + +import ( + "testing" +) + +func TestProcessExistsNonExistent(t *testing.T) { + // Using a very high PID that's unlikely to exist + pid := 999999999 + if ProcessExists(pid) { + t.Errorf("ProcessExists(%d) = true, want false for non-existent process", pid) + } +} + +func TestProcessExistsNegativePID(t *testing.T) { + // Negative PIDs are invalid and should return false or may cause errors + // depending on the platform, so just test that it doesn't panic + _ = ProcessExists(-1) +} + +func TestProcessExistsZero(t *testing.T) { + // PID 0 is special (kernel process on Unix) + // Test that we can call it without panicking + _ = ProcessExists(0) +} diff --git a/internal/witness/manager.go b/internal/witness/manager.go index 747d92ff..bcb3a8e8 100644 --- a/internal/witness/manager.go +++ b/internal/witness/manager.go @@ -90,7 +90,7 @@ func (m *Manager) Start() error { return err } - if w.State == StateRunning && w.PID > 0 && processExists(w.PID) { + if w.State == StateRunning && w.PID > 0 && util.ProcessExists(w.PID) { return ErrAlreadyRunning } @@ -128,13 +128,3 @@ func (m *Manager) Stop() error { return m.saveState(w) } -// processExists checks if a process with the given PID exists. -func processExists(pid int) bool { - proc, err := os.FindProcess(pid) - if err != nil { - return false - } - // On Unix, FindProcess always succeeds; signal 0 tests existence - err = proc.Signal(nil) - return err == nil -}