Repository navigation
fix: the framework lock is a queue of owned tickets - #376
Merged
Merged
Conversation
One shared lock file, broken by removing and re-creating its path, is a race between any two waiters that saw it stale (#344 closed one level of it and left the next), and a fixed 60 s age broke live holders — a publish across a full reindex reopened the version race #240 fixed. Each waiter now owns a ticket beside the lock, refreshed on a timer while it waits and holds; the holder is the first ticket whose owner is alive and refreshing; a dead owner's ticket is removed at once and a hung one's after STALE_LOCK_MS, each by its own unique name. Lamport's doorway keeps a waiter from being overtaken between taking its number and writing it. A pre-existing plain lock file is honoured while fresh. Closes #355 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TheAmericanMaker
force-pushed
the
fix/lock-tickets
branch
from
September 16, 2026 01:29
ff11ef2 to
1c144d6
Compare
This was referenced Sep 16, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #355. Verified before changing: the issue's two claims are true of
mainby construction —withRemovalLockbroke a stale.breakwith a barerm(the same race #344 fixed one level up), andacquireLockbroke any lock older thanSTALE_LOCK_MSwith no way for a live holder to say it was still there. The new "live holder paststaleMskeeps the lock" test cannot pass on the old design.The redesign replaces the shared file with owned tickets (Lamport's bakery on a filesystem):
<lock>.t.<order>-<pid>-<token>beside the lock — its own file, written by it alone — after announcing<lock>.c.<token>at the doorway, so a waiter that took an earlier number cannot be overtaken before its ticket exists.process.kill(pid, 0)) and whose ticket was refreshed withinstaleMs. Every owner refreshes its ticket on anunref'd timer (staleMs / 4) while it waits and while it holds — a live holder is never broken by age.staleMs, a dead chooser's marker at once — each by its unique name viaunlink, andbrokeStaleis recorded only by the caller whoseunlinkactually removed it (fs.promises.rmreports success to every concurrent caller, which is how the many-waiters test first reported eight breakers).lockPathfile from a pre-upgrade process is honoured while fresh and removed when stale, so an upgrade under a live older process does not let two writers in.acquireLock(lockPath, { timeoutMs, staleMs });release()clears the heartbeat and unlinks the ticket.BREAK_LOCK_STALE_MSand the removal lock are gone. The four call sites (status,usage,library,broadside/state) are unchanged.Test plan
tests/state-store.test.mjs, rewritten lock section: a live holder older thanstaleMsis not broken (the waiter times out instead); a dead owner's ticket is removed at once and a hung one's afterstaleMs, withbrokeStalerecorded; release removes only the releaser's ticket ([self-audit D-H5] Status lock release removes whoever's lock is present, including a newer holder's #227 kept); eight waiters behind a dead ticket — one holder at a time, all served, exactly one breaker, nothing left behind; a dead chooser's marker is cleared and a live one blocks until withdrawn; a fresh legacy lock blocks and a stale one is removed. Run four times, stable.npm run build+npm test— 923 pass (the concurrent-publish, usage-append, and completion-commit tests exercise the lock through its callers).🤖 Generated with Claude Code