Skip to content

fix(db): tolerate a concurrent WAL switch on a fresh SQLite file - #722

Merged
antosubash merged 2 commits into
mainfrom
fix/sqlite-wal-switch-race
Oct 31, 2026
Merged

antosubash merged 2 commits into
mainfrom
fix/sqlite-wal-switch-race

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Fixes a CI flake found while merging Dependabot PRs: test_concurrent_callers_keep_tenants_and_pools_separate (from #720) failed on #676's Postgres job with sqlite3.OperationalError: database is locked.

Cause. Every new SQLite connection ran PRAGMA journal_mode=WAL. Switching a rollback-journal file to WAL needs an exclusive lock, and SQLite refuses it at once, without waiting out busy_timeout, when another connection holds a lock. Two connections opening a fresh file together (two worker threads in that test; concurrent workers on a first boot in real use) could fail.

Fix (framework/db/simple_module_db/session.py, _ensure_wal):

  • Read the journal mode first, and skip the switch when the file is already WAL. WAL persists in the file, so every later connection skips it.
  • Retry a lost switch every 20 ms until busy_timeout runs out, then re-raise. A busy_timeout of 0 keeps the old fail-at-once behaviour.
  • On aiosqlite the retry sleep blocks the event loop, but only while a fresh file is being switched.

Tests (framework/db/tests/test_db_sqlite_wal_switch.py):

  • A real second connection holds a write lock on a fresh file. The old one-shot switch fails; _ensure_wal succeeds once that connection finishes.
  • Unit tests cover skip, lost race, retry, and give-up.
  • The flaky test passed 60 of 60 local loops (it failed 1 in 40 with only a re-read and no retry).

Checks: make lint passes; PYTEST_WORKERS=8 make test-py: 6686 passed. A Sonnet review found no bugs; both minor points it raised are addressed (the event-loop note and the real-race test).

https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

Two connections opening a fresh file at once raced on PRAGMA journal_mode=WAL,
which SQLite fails with 'database is locked' without waiting out busy_timeout.
Skip the switch when the file is already WAL and retry a lost switch until
busy_timeout runs out. Flaked test_concurrent_callers_keep_tenants_and_pools_separate.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-10-31T16:20:37.377741Z 99cf8c6 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying simple-module-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: 99cf8c6
Status: ✅  Deploy successful!
Preview URL: https://bfeeebc8.simple-module-python.pages.dev
Branch Preview URL: https://fix-sqlite-wal-switch-race.simple-module-python.pages.dev

View logs

@antosubash
antosubash merged commit dac1254 into main Oct 31, 2026
17 checks passed
@antosubash
antosubash deleted the fix/sqlite-wal-switch-race branch October 31, 2026 16:32
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