fix: race-proof writer-lock acquisition + machine-readable conflict through init
- acquireWriterLock now CLAIMS with an atomic create-exclusive write (O_EXCL) inside a bounded retry loop: two processes racing an absent lock can never both succeed (the old read-then-tmp-rename flow let the loser keep running unlocked, silently). An EEXIST loser re-evaluates and either throws loudly with the winner's details or performs a verified stale-takeover (re-read before unlink so a lock that changed hands mid-deliberation is never clobbered). Exhausted contention fails loudly instead of degrading into a lockless open. - BRAINY_WRITER_LOCKED passes through init() unwrapped: the error documents a machine-readable contract (err.code + err.lockInfo with the holder's pid/host/heartbeat), but init's blanket error wrapping stripped both, leaving consumers a message to regex against. - Two contract tests added: stale-foreign takeover installs OUR lock via the atomic claim; the conflict error carries code + lockInfo at the public init() surface.
This commit is contained in:
parent
e450e0eedf
commit
01a3b46ade
4 changed files with 188 additions and 49 deletions
20
RELEASES.md
20
RELEASES.md
|
|
@ -10,6 +10,26 @@ Full auto-generated changelog: `CHANGELOG.md` · Releases: https://github.com/so
|
|||
|
||||
---
|
||||
|
||||
## v8.7.1 — 2026-07-17 (writer-lock acquisition is race-proof + machine-readable through init)
|
||||
|
||||
Two hardenings of the multi-process writer lock (the `locks/_writer.lock` lease that makes a
|
||||
second writer on the same brain directory fail loudly):
|
||||
|
||||
- **Lock acquisition claims atomically.** The acquire path used read-then-write, leaving a
|
||||
window where two processes racing an *absent* lock could both "succeed" — and the loser
|
||||
kept running unlocked, silently. The claim is now an atomic create-exclusive write
|
||||
(`O_EXCL`): exactly one racer wins; the loser re-evaluates and either fails loudly with
|
||||
the winner's details or performs a verified stale-takeover. Bounded retries; contention
|
||||
beyond them fails loudly rather than degrading into a lockless open.
|
||||
- **`BRAINY_WRITER_LOCKED` survives `init()`.** The conflict error documents a
|
||||
machine-readable contract (`err.code`, `err.lockInfo` with the holder's pid/host/
|
||||
heartbeat), but init's error wrapping silently stripped both, leaving consumers a message
|
||||
to regex against. The error now passes through unwrapped.
|
||||
|
||||
Measured while verifying (for operators sizing audits): `brain.auditGraph()` at a
|
||||
production-consumer scale of ~2,600 relationships / 800 entities costs ~0.1 s warm and
|
||||
~0.5 s cold, with exact scar counting across reopen.
|
||||
|
||||
## v8.7.0 — 2026-07-17 (bulk-transact ergonomics: scaled budgets + timeout telemetry)
|
||||
|
||||
The bulk-import ergonomics release, from a consumer's measured production incident (a serial
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue