From 2adc7f60c06e1235638ed8dd447728432cd1edfa Mon Sep 17 00:00:00 2001 From: Colby Mchenry Date: Fri, 17 Jul 2026 09:11:32 -0500 Subject: [PATCH] =?UTF-8?q?fix(db):=20WAL=20truncate=20at=20parked=20barri?= =?UTF-8?q?ers=20ONLY=20=E2=80=94=20the=20timer-path=20truncate=20loses=20?= =?UTF-8?q?the=20race=20it=20was=20assumed=20to=20lose=20(#1336)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A truncate checkpoint started against an ACTIVE writer wins the lock and then blocks that writer for its entire backfill; after the edge-index recreate's multi-GB single-transaction burst that exceeds the writer's 5s busy_timeout and fails the index with 'database is locked' (§7a.2 record run, EXIT=1 at kernel scale — the small mid-resolution truncates folded in ms and masked the hazard). Barrier truncates (backpressure/foldNow) are collision-free by construction: the writer is awaiting the valve. Dubbo gate: exit 0, peak 81MB (barrier folds carry containment), dump byte-identical. Valve + sizing suites 27/27. Co-authored-by: Claude Fable 5 --- src/db/wal-valve.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/db/wal-valve.ts b/src/db/wal-valve.ts index 7ebf0ca..91f4ffc 100644 --- a/src/db/wal-valve.ts +++ b/src/db/wal-valve.ts @@ -263,16 +263,14 @@ export class WalCheckpointValve { // SQLite reports log = checkpointed = -1, which is harmless here. if (res && res.busy === 0 && res.log === res.checkpointed) { this.sizeAtLastFullBackfill = this.db.getWalSizeBytes(); - // Opportunistic file chop while everything is folded: best-effort — - // an active writer/reader turns it into a busy no-op, and the - // barrier-path truncate (backpressure file cap) remains the - // deterministic bound. - if (this.db.getWalSizeBytes() > this.softBytes * 2) { - return this.db.checkpointWalTruncate().then((t) => { - if (t) this.log(`timer truncate: busy=${t.busy} wal=${this.mb(this.db.getWalSizeBytes())}`); - this.sizeAtLastFullBackfill = this.db.getWalSizeBytes(); - }); - } + // NO truncate here. A truncate checkpoint that starts against an + // ACTIVE writer wins the lock race and then blocks that writer for + // its entire backfill — after a multi-GB single-transaction burst + // (edge-index recreate) that exceeds the writer's 5s busy_timeout + // and fails the index with "database is locked" (§7a.2 record run). + // The file chop happens exclusively at parked barriers + // (backpressure/foldNow), where the writer is awaiting us by + // construction and cannot collide. } }) .catch(() => { /* best-effort */ })