Repository navigation
fix(fsUtils): fail the critical section on a compromised lock, not a timer throw - #100
Merged
1 commit merged intoOct 8, 2026
Merged
1 commit merged into
1 commit merged into
Conversation
…timer throw Mechanism, verified against [email protected] `lib/lockfile.js`: `updateLock` refreshes a held lock's mtime on an `unref`'d timer; when that `stat`/`utimes` ENOENTs it calls `setLockAsCompromised` with `Object.assign(err, { code: 'ECOMPROMISED' })` — the error is rewritten IN PLACE, so its `ENOENT … stat '…/<target>.lock'` text survives. The DEFAULT `onCompromised` is `(err) => { throw err }`, i.e. an uncaught exception raised from that timer callback, with no call-stack relationship to the code holding the lock: under jest it fails whatever test happens to be running, while the caller still inside the critical section is never told it lost exclusivity. `setLockAsCompromised` also marks the holder released before invoking the handler, which is why `release()` then rejects `ERELEASED` (already handled). The fix: `withFileLock` owns `onCompromised`. It logs, then rejects a `Deferred` the call races against the section, so the compromise fails THAT call with a `NestedError` carrying proper-lockfile's error as the cause and `{ lockPath }` as context. A compromise landing after the section already settled is swallowed; a section that fails after a compromise decided the outcome is logged at warn instead of vanishing. The section itself cannot be cancelled — the public JSDoc now says so, since its side effects still land. Deliberately NOT changed: `stale`, `update` and `retries` stay exactly as master had them. Across four full cluster-tool runs the compromise signature appeared 18–68 times while `Unable to update lock within the stale threshold` — the signature a stale STEAL produces — appeared ZERO times: every observed compromise was the lock directory being deleted under a live holder, so the handler, not the threshold, is what fixes the reported failure. Raising `stale` would only lengthen how long a crashed holder wedges every contender. Gate on an idle host: `pnpm build` clean, `pnpm run lint` clean, `pnpm --filter @wireio/cluster-tool test` 138/138 suites, 1505/1505 tests, with no `.lock.lock` signature anywhere in the run. The new compromise test fails against the unpatched handler (uncaught `ENOENT … .lock` from the refresh timer; the call never settles), and the root `jest.config.ts` note about that hook throwing is updated to what happens now. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01CNDguHHWTwn1UBejDiTAGa
Contributor
Author
|
CI note: the |
valthon
added a commit
that referenced
this pull request
Oct 2, 2026
Combine owning-call compromise failures with PR #76's registry-scoped external mutex and 30-second short-lock stale threshold. Preserve the long-lock policy and document that lock loss cannot cancel ongoing work. Retain faster wildcard port probing with IPv6 checks, early range rejection, two Jest workers, and a supervised full commit gate. Isolate the validator environment test and keep all test ports in the bind registry. Cover real lock refresh and mutual exclusion after registry deletion, late completion after compromise, network-family collisions, and watchdog termination of descendants.
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
withFileLockleft proper-lockfile's defaultonCompromisedin place, and that default re-throws from the library's ownunref'd mtime-refresh timer. When a held lock's directory disappears under a live holder, the refresh'sstatfails withENOENT, proper-lockfile rewrites the error in place toECOMPROMISED(keeping theENOENT … wire-cluster-ports.lock.locktext), and the throw lands as an uncaught exception with no relationship to the caller that owns the lock. Under jest that fails whichever test happens to be running, which is why the bind-registry signature kept surfacing in a different suite each time (ClusterBuildDefaultsEpochBootstrap,NodeConfig,OperatorFunding,ClusterConfigProvider) and passed on retry.The fix is the handler and nothing else:
withFileLockownsonCompromised: the in-flight critical section rejects with aNestedErrorcarrying proper-lockfile's error as the cause, so the call that lost exclusivity is the call that fails. The section itself is not cancelled and its side effects are not undone; the public JSDoc says so.stale,update, retries) are unchanged from master. Every observed compromise on this host was the lock directory being deleted under a live holder (a test aborted at its 120 s budget while its fixture cleanup removed the sandboxed registry dir); the stale-steal signature appeared zero times, so no threshold tuning is warranted here.jest.config.tscomment that still described the timer throw is updated.Diff: 3 files, +161/−8. Mechanism verified against proper-lockfile 4.1.2 source and reproduced with a standalone probe before the fix.
Verification
pnpm build,pnpm run lintpnpm test(full, idle host, on master2d703304)lock.locksignatures in the runtests/utils/fsUtils.test.ts🤖 Generated with Claude Code
https://claude.ai/code/session_01CNDguHHWTwn1UBejDiTAGa