From 30310beb26c26f2f5326273a07681f9991194d7e Mon Sep 17 00:00:00 2001 From: Andy Pai Date: Wed, 8 Apr 2026 20:33:33 -0400 Subject: [PATCH] fix: shorten write transaction lifetimes to resolve sync lock contention The watcher pipeline held write transactions open across async yields and idle periods, preventing WAL checkpointing and causing periodic sync to fail with "database is locked" on every cycle. - Flush after each event in processDue() so no transaction spans an await boundary - Flush after on_checkpoint writes so no transaction lingers for up to 60s - Flush at start of syncOnce() to commit any pending pipeline work before sync reads/writes (intentional cross-subsystem commit) --- src/runtime/watch-pipeline-ingest.ts | 1 + src/runtime/watch-pipeline-processor.ts | 1 + src/sync/sync-engine.ts | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/src/runtime/watch-pipeline-ingest.ts b/src/runtime/watch-pipeline-ingest.ts index 05b6a8e..18bd5b8 100644 --- a/src/runtime/watch-pipeline-ingest.ts +++ b/src/runtime/watch-pipeline-ingest.ts @@ -133,6 +133,7 @@ export function createWatchIngest(options: WatchIngestOptions): WatchIngest { poll_interval_ms: cfg.watch.poll_interval_ms, on_checkpoint: (ts) => { db.setState('file_watcher.checkpoint_ts', String(ts)) + db.flush() }, }) diff --git a/src/runtime/watch-pipeline-processor.ts b/src/runtime/watch-pipeline-processor.ts index adc51fc..1b36d77 100644 --- a/src/runtime/watch-pipeline-processor.ts +++ b/src/runtime/watch-pipeline-processor.ts @@ -67,6 +67,7 @@ export function createWatchPipelineProcessor(ingest: WatchIngest): WatchPipeline try { await db.insert(enriched) db.setState('events.last_ts', String(enriched.timestamp)) + db.flush() stats.stored++ } catch (err) { stats.errors++ diff --git a/src/sync/sync-engine.ts b/src/sync/sync-engine.ts index 0860aa1..f16c797 100644 --- a/src/sync/sync-engine.ts +++ b/src/sync/sync-engine.ts @@ -48,6 +48,11 @@ export class SyncEngine { } try { + // Commit any pending pipeline transaction so sync operates on a clean + // connection state. This is an intentional cross-subsystem commit: + // each pipeline event is an independent unit, so force-committing + // partial pipeline work is safe and prevents write-lock contention. + this.options.db.flush() const cfg = this.options.config.sync if (!cfg?.server_url) { result.errors.push('sync.server_url not configured')