fix(durability): three block-layer power-loss findings from the first fault-injection box run — all cured, matrix 15/15
An internal cross-engine fault-injection run (frozen-platter power-loss capture) surfaced three release-gating findings; each cured in its owning layer, each pinned: 1. WHOLE-LOG REPLAY ON UNCLEAN OPEN (the big one): log-authority replay only covered facts ABOVE the manifest — but live canonical entity writes are tmp+rename without per-file fsync, and the group-commit flush syncs staging + manifest, never the live tree. Power loss could therefore vaporize acked canonical bytes BELOW the manifest while the log held every fact scan-clean (measured: 299 of 301 acks lost). Now: a clean close stamps a clean-shutdown marker (fsynced, written last); every open consumes it; an UNCLEAN open under log authority folds the ENTIRE log into canonical — whole-entity after-images make the re-apply idempotent and byte-safe. Zero cost on the happy path; crash recovery pays one narrated fold. Recovery is replay: a crash is just bigger lag. 2. TORN WRITER LOCK: power loss legally leaves the lock file present but empty; the parse failure read as 'no holder' while the O_EXCL claim EEXISTed forever — a PERMANENT lockout no staleness check could clear. An unparseable lock is stale by definition (no live holder has one): unlink loudly and re-loop; a racer rewriting a valid lock first wins. 3. PAIR GUARD: flush() called metadataIndex.stampWatermark unguarded; a replacement metadata provider without the method killed the pair at first flush. All three stamp calls are optional-chained — a missing stamp is a verdict-side rescan, never a flush crash. Pins: whole-log fold restores rows vanished below the manifest · clean-shutdown marker lifecycle (stamp/consume/re-stamp) · torn-lock recovery with a fresh write after · stampless-provider flush. Gates: unit 2055/2055 · integration 824 · kill-matrix 15/15.
This commit is contained in:
parent
d1698fa5be
commit
67c606be69
4 changed files with 180 additions and 14 deletions
|
|
@ -11247,7 +11247,10 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
{
|
||||
const wmGen = this.storage?.committedGeneration?.() ?? null
|
||||
if (wmGen !== null) {
|
||||
this.metadataIndex.stampWatermark(wmGen)
|
||||
// ALL THREE optional-chained: a replacement provider (the native
|
||||
// pair swaps these managers) may not carry the stamp method — a
|
||||
// missing stamp is a verdict-side rescan, never a flush crash.
|
||||
;(this.metadataIndex as { stampWatermark?: (g: number) => void }).stampWatermark?.(wmGen)
|
||||
;(this.index as { stampWatermark?: (g: number) => void }).stampWatermark?.(wmGen)
|
||||
;(this.graphIndex as { stampWatermark?: (g: number) => void }).stampWatermark?.(wmGen)
|
||||
}
|
||||
|
|
|
|||
Reference in a new issue