Fix bd-54: Prevent multiple daemon instances via file locking

- Implemented daemon.lock using flock (Unix) and LockFileEx (Windows)
- Lock acquired before PID file, held for daemon lifetime
- Eliminates race conditions in concurrent daemon starts
- Backward compatible: falls back to PID check for old daemons
- Updated isDaemonRunning() to check lock availability
- All tests pass including new lock and backward compatibility tests

Amp-Thread-ID: https://ampcode.com/threads/T-0e2627f4-03f9-4024-bb4b-21d23d296300
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-10-22 13:59:21 -07:00
parent d1d3fcdf02
commit e291f464ee
9 changed files with 475 additions and 76 deletions

View File

@@ -128,14 +128,18 @@ func TestIsDaemonRunning_CurrentProcess(t *testing.T) {
tmpDir := t.TempDir()
pidFile := filepath.Join(tmpDir, "test.pid")
currentPID := os.Getpid()
if err := os.WriteFile(pidFile, []byte(strconv.Itoa(currentPID)), 0644); err != nil {
t.Fatalf("Failed to write PID file: %v", err)
// Acquire the daemon lock to simulate a running daemon
beadsDir := filepath.Dir(pidFile)
lock, err := acquireDaemonLock(beadsDir, false)
if err != nil {
t.Fatalf("Failed to acquire daemon lock: %v", err)
}
defer lock.Close()
currentPID := os.Getpid()
isRunning, pid := isDaemonRunning(pidFile)
if !isRunning {
t.Error("Expected daemon running (current process PID)")
t.Error("Expected daemon running (lock held)")
}
if pid != currentPID {
t.Errorf("Expected PID %d, got %d", currentPID, pid)