Files
beads/internal/lockfile/lock_windows.go
Steve Yegge 1d5fd64383 fix(daemon): add cross-process locking to registry (bd-5bj)
The global daemon registry (~/.beads/registry.json) could be corrupted
when multiple daemons from different workspaces wrote simultaneously.

Changes:
- Add file locking (flock) for cross-process synchronization
- Use atomic writes (temp file + rename) to prevent partial writes
- Keep entire read-modify-write cycle under single lock
- Add FlockExclusiveBlocking and FlockUnlock to lockfile package

This prevents race conditions that caused JSON corruption like `]]`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 14:22:42 -08:00

70 lines
1.5 KiB
Go

//go:build windows
package lockfile
import (
"errors"
"os"
"syscall"
"golang.org/x/sys/windows"
)
var errDaemonLocked = errors.New("daemon lock already held by another process")
// flockExclusive acquires an exclusive non-blocking lock on the file using LockFileEx
func flockExclusive(f *os.File) error {
// LOCKFILE_EXCLUSIVE_LOCK (2) | LOCKFILE_FAIL_IMMEDIATELY (1) = 3
const flags = windows.LOCKFILE_EXCLUSIVE_LOCK | windows.LOCKFILE_FAIL_IMMEDIATELY
// Create overlapped structure for the entire file
ol := &windows.Overlapped{}
// Lock entire file (0xFFFFFFFF, 0xFFFFFFFF = maximum range)
err := windows.LockFileEx(
windows.Handle(f.Fd()),
flags,
0, // reserved
0xFFFFFFFF, // number of bytes to lock (low)
0xFFFFFFFF, // number of bytes to lock (high)
ol,
)
if err == windows.ERROR_LOCK_VIOLATION || err == syscall.EWOULDBLOCK {
return errDaemonLocked
}
return err
}
// FlockExclusiveBlocking acquires an exclusive blocking lock on the file.
// This will wait until the lock is available.
func FlockExclusiveBlocking(f *os.File) error {
// LOCKFILE_EXCLUSIVE_LOCK only (no FAIL_IMMEDIATELY = blocking)
const flags = windows.LOCKFILE_EXCLUSIVE_LOCK
ol := &windows.Overlapped{}
return windows.LockFileEx(
windows.Handle(f.Fd()),
flags,
0,
0xFFFFFFFF,
0xFFFFFFFF,
ol,
)
}
// FlockUnlock releases a lock on the file.
func FlockUnlock(f *os.File) error {
ol := &windows.Overlapped{}
return windows.UnlockFileEx(
windows.Handle(f.Fd()),
0,
0xFFFFFFFF,
0xFFFFFFFF,
ol,
)
}