fix(daemon): prevent orphan daemons via file locking

Add syscall.Flock() exclusive lock in daemon.Run() to prevent TOCTOU
race condition where concurrent 'gt daemon start' commands could spawn
multiple daemons. Only the first to acquire the lock succeeds; others
exit cleanly. Lock is per-town (in townRoot/daemon/daemon.lock) so
multiple GT instances from different directories work independently.

Also detect race losers in runDaemonStart() by comparing spawned PID
with PID file, reporting 'already running' instead of false success.
This commit is contained in:
jakehemmerle
2026-01-04 15:06:23 -05:00
parent 1f90882a0d
commit c25ff34b5b
2 changed files with 34 additions and 4 deletions

View File

@@ -117,7 +117,7 @@ func runDaemonStart(cmd *cobra.Command, args []string) error {
return fmt.Errorf("starting daemon: %w", err)
}
// Wait a moment for the daemon to initialize
// Wait a moment for the daemon to initialize and acquire the lock
time.Sleep(200 * time.Millisecond)
// Verify it started
@@ -129,6 +129,15 @@ func runDaemonStart(cmd *cobra.Command, args []string) error {
return fmt.Errorf("daemon failed to start (check logs with 'gt daemon logs')")
}
// Check if our spawned process is the one that won the race.
// If another concurrent start won, our process would have exited after
// failing to acquire the lock, and the PID file would have a different PID.
if pid != daemonCmd.Process.Pid {
// Another daemon won the race - that's fine, report it
fmt.Printf("%s Daemon already running (PID %d)\n", style.Bold.Render("●"), pid)
return nil
}
fmt.Printf("%s Daemon started (PID %d)\n", style.Bold.Render("✓"), pid)
return nil
}