Repository navigation
fix(lock): sleep and report the open error when no guard was ever seen - #227
Merged
Merged
Conversation
On a read-only lock directory open(wx) fails with EPERM/EACCES and the following stat finds no guard (ENOENT). The age === null branch retried without sleeping, so acquire() busy-looped a full core for the whole 10 s window and then reported a timeout waiting for another truecopy process about a guard that never existed. That branch now sleeps like every other wait. acquire() records whether a guard was ever observed (EEXIST from open or a successful stat) and the last open error; at the deadline with no guard observed it throws the open error instead of the phantom-holder timeout. Tests inject EACCES and EPERM on open(wx) with the real stat and assert the original error surfaces and open is attempted fewer than 100 times in a 300 ms window.
sprayberry-redline
approved these changes
Sep 26, 2026
sprayberry-redline
left a comment
Collaborator
There was a problem hiding this comment.
Verdict: approve. The age === null branch now falls through to the shared 20 ms sleep instead of continuing, and acquire() reports the original open error at the deadline when no guard was ever observed, while an EEXIST that races away still yields the holder timeout. Traced both paths through the loop; the new EACCES/EPERM tests fail on the previous code (which threw the timeout message) and the attempt-count bound proves the sleep.
Merged
askalf
added a commit
that referenced
this pull request
Oct 29, 2026
Bumps the version to 0.10.5. On merge, auto-release.yml tags v0.10.5, cuts the GitHub release from the new CHANGELOG section and dispatches publish.yml to npm. Ships the --version fix (#211), the read-only lock directory fix (#227, now in the CHANGELOG) and the README rewrite (#316), which npm shows only from a release. The pinned version in the docs and issue templates moves with it; docker/ stays on 0.10.4 until 0.10.5 is on npm.
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.
Follow-up to #197.
Problem
On a read-only lock directory,
open(guard, 'wx')fails with EPERM/EACCES (treated as contention) and the followingstat(guard)returns ENOENT because the guard was never created. Theage === nullbranch didcontinue, skippingsleepSync, soacquire()busy-looped for the full 10 s window (measured with a deny-write ACL: 10002 ms elapsed, 9985 ms CPU; about 160k open attempts under injection). It then threw "timed out waiting for another truecopy process ... delete it if no truecopy is running" about a guard that never existed.Fix
age === nullbranch falls through to the samesleepSync(20)as every other wait.acquire()tracks whether a guard was ever observed (EEXIST from open, or a successful stat) and the last open error. At the deadline, if no guard was observed, it throws the original open error instead of the holder timeout. The EEXIST-then-ENOENT race still ends in the timeout message.Tests
test/lock-guard-windows.test.mjs: two new cases (EACCES, EPERM) inject the error onopen(wx)with the realstat, and assert the original code surfaces (not "timed out waiting") and that open is attempted fewer than 100 times in a 300 ms window. Both fail on main with the phantom-holder timeout.Full suite: 277 tests, 273 pass, 0 fail, 4 skipped (Windows 11, Node).