Files
beads/internal/types/process.go
Steve Yegge 3a42ca252d 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>
2025-10-25 23:32:47 -07:00

48 lines
1.3 KiB
Go

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
}