feat(v0.6): IsStale supplements wall-clock freshness with PID liveness

This commit is contained in:
2026-05-15 14:26:15 -04:00
parent 9070c4274c
commit f379a6d059
2 changed files with 136 additions and 0 deletions
+35
View File
@@ -131,6 +131,41 @@ func IsFresh(l *Lease, now time.Time, threshold time.Duration) bool {
return now.Sub(l.LastHeartbeatAt) <= threshold
}
// IsStale reports whether the lease should be treated as stale. It
// supplements the wall-clock heartbeat threshold with PID liveness:
//
// - A heartbeat older than threshold is stale (existing behavior).
// - Otherwise, for a lease whose hostname matches the current host and
// whose PID is valid (> 0), a dead owner PID makes the lease stale
// immediately. This is the v0.6 lazy-cleanup signal: a Ctrl-C'd or
// terminal-closed session is recognized without the 60s wall-clock
// wait.
// - Remote leases, leases with pid <= 0, and inconclusive PID checks
// (ProcessUnknown) fall back to wall-clock freshness only.
//
// PID liveness can only flip a wall-clock-fresh lease to stale; it never
// revives a wall-clock-stale lease. IsStale is the freshness predicate
// for adoption and Layer-1 decisions — prefer it over a bare IsFresh
// call at any site that decides whether a session is still owned.
func IsStale(l *Lease, now time.Time, threshold time.Duration) bool {
if l == nil {
return true
}
if !IsFresh(l, now, threshold) {
return true // wall-clock stale — existing behavior
}
if l.PID <= 0 {
return false // no usable PID — wall-clock only
}
if l.Hostname != currentHostname() {
return false // remote lease — wall-clock only
}
if checkProcess(l.PID) == ProcessDead {
return true // local owner confirmed dead
}
return false // alive or inconclusive — conservative
}
// CleanupStaleLease inspects the lease at path:
// - missing file: no-op, returns (nil, nil)
// - corrupt / unparseable: removes the file, returns (nil, nil)