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
|
|
@ -1373,6 +1373,13 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
if (this._readyReject) {
|
||||
this._readyReject(error instanceof Error ? error : new Error(String(error)))
|
||||
}
|
||||
// Machine-readable init failures pass through UNWRAPPED — the writer-lock
|
||||
// conflict documents an err.code/err.lockInfo contract ("callers detect
|
||||
// this case via err.code"), and wrapping in a fresh Error silently
|
||||
// stripped both, leaving consumers only a message to regex against.
|
||||
if (error instanceof Error && (error as Error & { code?: string }).code === 'BRAINY_WRITER_LOCKED') {
|
||||
throw error
|
||||
}
|
||||
throw new Error(`Failed to initialize Brainy: ${error}`)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue