diff --git a/pkg/daemon/pidfile.go b/pkg/daemon/pidfile.go index 0ee026c59..a68f3356b 100644 --- a/pkg/daemon/pidfile.go +++ b/pkg/daemon/pidfile.go @@ -44,24 +44,30 @@ func NewPIDFile(path string) *PIDFile { // This prevents multiple instances of the gateway from running simultaneously, // which could cause resource conflicts and undefined behavior. func (p *PIDFile) Write() error { + return p.WritePID(os.Getpid()) +} + +// WritePID atomically writes the specified process ID to the PID file. +// Returns an error if a PID file already exists with a running process. +// +// Use this method when you need to write a PID other than the current process, +// such as when spawning a child process. +func (p *PIDFile) WritePID(pid int) error { p.mu.Lock() defer p.mu.Unlock() // Check for existing PID file if _, err := os.Stat(p.path); err == nil { // PID file exists, check if process is running - pid, err := p.read() - if err == nil && p.isProcessRunning(pid) { + existingPid, err := p.read() + if err == nil && p.isProcessRunning(existingPid) { return &ProcessRunningError{ - pid: pid, + pid: existingPid, Path: p.path, } } // Process is not running, stale PID file, continue } - - // Write PID to temp file in same directory (atomic preparation) - pid := os.Getpid() pidStr := strconv.Itoa(pid) tempFile := p.path + ".tmp" diff --git a/pkg/daemon/service.go b/pkg/daemon/service.go index 1dd95ec28..b91cbb39c 100644 --- a/pkg/daemon/service.go +++ b/pkg/daemon/service.go @@ -150,14 +150,24 @@ func (s *Service) Start() error { pid := cmd.Process.Pid - // Write PID file atomically - if err := s.pidFile.Write(); err != nil { + // Write PID file atomically with the child's PID + if err := s.pidFile.WritePID(pid); err != nil { // Failed to write PID file, kill the process cmd.Process.Kill() logFile.Close() return fmt.Errorf("failed to write PID file: %w", err) } + // Verify the child process is actually running + // This prevents race conditions where status is checked immediately + time.Sleep(100 * time.Millisecond) + if !s.pidFile.IsProcessRunning() { + // Child process died immediately, likely a startup error + s.pidFile.Remove() + logFile.Close() + return fmt.Errorf("gateway process failed to start (check log file: %s)", s.logConfig.Path) + } + // Update state s.state.SetPID(pid) s.state.SetStartTime(time.Now())