Skip to content

fix(store): set busy_timeout before journal_mode=WAL to prevent immediate SQLITE_BUSY#117

Merged
DeusData merged 1 commit intoDeusData:mainfrom
halindrome:fix/wal-busy-timeout-ordering
Mar 23, 2026
Merged

fix(store): set busy_timeout before journal_mode=WAL to prevent immediate SQLITE_BUSY#117
DeusData merged 1 commit intoDeusData:mainfrom
halindrome:fix/wal-busy-timeout-ordering

Conversation

@halindrome
Copy link
Copy Markdown
Contributor

Summary

  • Reorders SQLite pragma execution in configure_pragmas() so busy_timeout=10000 is set before journal_mode=WAL
  • Eliminates the zero-timeout window between sqlite3_open_v2 and the first pragma execution
  • Ensures the WAL mode transition (which requires an exclusive lock) can retry for up to 10 seconds under lock contention

Fixes #116

Root cause

sqlite3_open_v2 returns a live connection with SQLite's default zero busy timeout. The prior ordering set busy_timeout as the third pragma — after journal_mode=WAL. Any lock contention during the WAL transition caused an immediate SQLITE_BUSY failure with no retry.

Change

// Before (store.c configure_pragmas):
exec_sql(s, "PRAGMA journal_mode = WAL;");
exec_sql(s, "PRAGMA synchronous = NORMAL;");
exec_sql(s, "PRAGMA busy_timeout = 10000;");

// After:
exec_sql(s, "PRAGMA busy_timeout = 10000;");
exec_sql(s, "PRAGMA journal_mode = WAL;");
exec_sql(s, "PRAGMA synchronous = NORMAL;");

Test plan

  • scripts/test.sh passes (all ~2040 cases)
  • scripts/lint.sh passes (clang-tidy, cppcheck, clang-format)
  • Manual: start MCP server indexing a large repo, then run a CLI query against the same project — no SQLITE_BUSY error

…iate SQLITE_BUSY on lock contention

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@halindrome
Copy link
Copy Markdown
Contributor Author

QA Round 1 — fix/wal-busy-timeout-ordering

Commit reviewed: 804d680
Method: Fresh model session, 10-point review
Verdict: APPROVE — no blocking findings

# Area Severity Result
1 Correctness: busy_timeout applies connection-wide before WAL pragma ✓ CORRECT — PRAGMA busy_timeout calls sqlite3_busy_timeout() on the connection; applies to all subsequent operations including DDL
2 Completeness: other sqlite3_open call sites INFO ✓ All clear — cli.c config DB is single-owner (no WAL, no contention); dump path temp file is exclusively owned at pragma time
3 Scope: commit touches only src/store/store.c ✓ CLEAN — git show 804d680 --stat confirms 1 file, 6 lines
4 Test coverage: no busy_timeout assertion test MINOR Gap exists — no test queries PRAGMA busy_timeout after cbm_store_open_path(). Not a blocker; could be a follow-up
5 Error handling: early return on busy_timeout failure ✓ CORRECT — WAL pragma never reached if busy_timeout fails
6 In-memory path skips busy_timeout and WAL ✓ CORRECT — :memory: DBs have no WAL mode and no concurrent access
7 SQLite documentation alignment ✓ CONFIRMED — matches documented sqlite3_busy_timeout() semantics
8 Dump path WAL pragma (line 554, dest_db) ✓ NOT VULNERABLE — temp file is exclusively owned; no contention possible
9 Branch hygiene ✓ CLEAN — single commit, correct format, no merge commits
10 Pre-existing: silenced error in dump path WAL pragma INFO Pre-existing pattern, out of scope for this PR

Notes:

  • The fix is mechanically correct. PRAGMA busy_timeout before PRAGMA journal_mode = WAL ensures the WAL lock acquisition can retry for up to 10 seconds rather than failing immediately.
  • The existing WAL invariant tests (bulk_pragma_wal_invariant, etc.) still pass. The ordering change does not affect test behavior.
  • The busy_timeout test gap (Finding 4) could be addressed with a simple post-cbm_store_open_path() PRAGMA busy_timeout query assertion in test_store_*.c — suggested as a follow-up, not a merge requirement.

@DeusData DeusData added bug Something isn't working stability/performance Server crashes, OOM, hangs, high CPU/memory labels Mar 23, 2026
@DeusData DeusData merged commit b4a9512 into DeusData:main Mar 23, 2026
@DeusData
Copy link
Copy Markdown
Owner

Merged to main. Fixes #116. Thanks @halindrome — correct and minimal fix. Setting busy_timeout before the WAL transition eliminates the zero-timeout window that caused SQLITE_BUSY under lock contention.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working stability/performance Server crashes, OOM, hangs, high CPU/memory

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(store): busy_timeout set after journal_mode=WAL — unprotected SQLITE_BUSY window on open

3 participants