Skip to content

feat(store): set busy_timeout on sqlite connections to avoid SQLITE_BUSY race - #732

Closed
fxstein wants to merge 1 commit into
tobi:mainfrom
fxstein:feat/sqlite-busy-timeout
Closed

fxstein wants to merge 1 commit into
tobi:mainfrom
fxstein:feat/sqlite-busy-timeout

Conversation

@fxstein

@fxstein fxstein commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

What

Sets PRAGMA busy_timeout = 10000 (10s) on every SQLite connection qmd opens, at the single openDatabase() choke point in src/db.ts.

Why

When two processes share the same qmd store — e.g. a long-running MCP daemon and a qmd CLI invocation (often driven from a short-interval cron) — the second writer hits SQLITE_BUSY immediately and aborts, because SQLite's default is to error rather than wait on a locked database:

SQLiteError: database is locked
  code: "SQLITE_BUSY"
  at clearCache (.../store.js) ... at updateCollections (.../cli/qmd.js)

busy_timeout makes SQLite wait-and-retry for up to the timeout before giving up — the standard pattern for short-lived multi-process contention. 10s comfortably covers the typical write window of either side.

Details

  • One-line change in openDatabase(), the only place qmd creates a connection (verified — the sole other open site routes through it). Applies uniformly under both bun:sqlite and better-sqlite3.
  • No behaviour change for single-process use.
  • tsc --noEmit clean.

…USY race

When two processes share the same qmd sqlite store — for example a
long-running MCP daemon and a `qmd` CLI invocation (often driven from a
short-interval cron) — the second writer hits SQLITE_BUSY immediately and
aborts, because sqlite's default behaviour is to error rather than wait on a
locked database.

Set PRAGMA busy_timeout = 10000 (10s) in openDatabase() so every connection
qmd opens wait-and-retries for up to 10 seconds before giving up. That covers
the typical write window of either side and lets concurrent operations
succeed without external coordination. This is the standard sqlite pattern
for short-lived multi-process contention.

The pragma is set at the single openDatabase() choke point, so it applies
uniformly under both bun:sqlite and better-sqlite3.
@fxstein

fxstein commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Closing as superseded by upstream. Since this PR was opened, main gained a more complete busy_timeout implementation in src/db.ts openDatabase():

That covers the SQLITE_BUSY race this PR was targeting and then some, so there's nothing left for this change to add. Thanks!

@fxstein fxstein closed this Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant