diff --git a/bin/gstack-lease b/bin/gstack-lease index 180c78e46..ded79313e 100755 --- a/bin/gstack-lease +++ b/bin/gstack-lease @@ -121,7 +121,10 @@ holder_gone() { case "$(state "${1%% *}" "${1#* }" 0)" in dead|stale) return 0 ; # break_lock OWNER — remove the lock only if it still names OWNER. Breakers # take a second symlink, .lock.break, and reread the lock under it, so a lock # taken since the verdict is never removed; a .lock.break whose holder is gone -# is cleared. Returns 1 when another breaker holds .lock.break. +# is cleared. Returns 1 when another breaker holds .lock.break. Known limit: +# that clear is not atomic, so two gstack-lease processes killed in a row (one +# holding .lock, the next inside this function) can let a reclaim delete a +# renewal; one crash is always safe. break_lock() { if ! ln -s "$TOKEN" "$LOCK.break" 2>/dev/null; then _b=$(readlink "$LOCK.break" 2>/dev/null) diff --git a/docs/capy-install.md b/docs/capy-install.md index efca9042b..b4d0f65c6 100644 --- a/docs/capy-install.md +++ b/docs/capy-install.md @@ -94,7 +94,9 @@ stale) run under one lock in the lease directory, a symlink naming its holder's pid and start identity, so a renewal is never deleted and a live lease never disappears from a concurrent scan. The lock is broken only when its holder is dead or its pid was reused, never by age, and breakers serialize on a second -symlink and reread the lock before removing it. While a live process holds it, +symlink and reread the lock before removing it. One killed gstack-lease process +is always safe; two killed in a row, one holding the lock and the next while +breaking it, can still let a reclaim delete a renewal. While a live process holds it, `touch` exits 3 without writing (`gstack-skill-start` reports that the session holds no lease) and a reclaim keeps the lease. `bin/gstack-lease list` shows `alive|dead|stale`, and `GSTACK_UPGRADE_FORCE=1` overrides.