Implement exclusive lock protocol for daemon/external tool coexistence
- Add ExclusiveLock struct with JSON marshaling and validation - Implement IsProcessAlive() with EPERM fail-safe behavior - Add ShouldSkipDatabase() with stale lock cleanup - Integrate lock checking into daemon sync cycle - Return holder name on stale removal for better logging - Case-insensitive hostname comparison - Comprehensive unit tests (89.3% coverage) - Documentation updates (ADVANCED.md, AGENTS.md) - Add .beads/.exclusive-lock to .gitignore Closes bd-115, bd-116, bd-117, bd-118, bd-119, bd-120, bd-121, bd-122 Amp-Thread-ID: https://ampcode.com/threads/T-0b835739-0d79-4ef9-aa62-8446a368c42d Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
47
internal/types/process.go
Normal file
47
internal/types/process.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// IsProcessAlive checks if a process with the given PID is alive on the given hostname.
|
||||
// If hostname doesn't match the current host, it returns true (cannot verify remote, assume alive).
|
||||
// If hostname matches the current host, it checks if the PID exists.
|
||||
// Permission errors are treated as "alive" (fail-safe: better to skip than wrongly remove a lock).
|
||||
func IsProcessAlive(pid int, hostname string) bool {
|
||||
currentHost, err := os.Hostname()
|
||||
if err != nil {
|
||||
// Can't determine current hostname, assume process is alive (fail-safe)
|
||||
return true
|
||||
}
|
||||
|
||||
// Case-insensitive hostname comparison to handle FQDN vs short name differences
|
||||
if !strings.EqualFold(hostname, currentHost) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if process exists on local host
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
// On Unix, FindProcess always succeeds, so this is unlikely
|
||||
return false
|
||||
}
|
||||
|
||||
// Send signal 0 to check if process exists without actually sending a signal
|
||||
err = process.Signal(syscall.Signal(0))
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Only mark as dead on ESRCH (no such process)
|
||||
// EPERM (permission denied) and other errors => assume alive (fail-safe)
|
||||
var errno syscall.Errno
|
||||
if errors.As(err, &errno) && errno == syscall.ESRCH {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user