{{ message }}
fix(db): serialize database init and migrations across processes - #44558
Open
gitRasheed wants to merge 6 commits into
Open
fix(db): serialize database init and migrations across processes#44558gitRasheed wants to merge 6 commits into
gitRasheed wants to merge 6 commits into
Conversation
Concurrent CLI processes racing one database file crash at startup: the driver enables WAL at connection-open before any busy_timeout is set, so the losers fail instantly with 'database is locked', and the migration path guards its check-then-write with an in-process semaphore only, letting two fresh processes both run schema.up — or both run a pending migration, which for destructive migrations means data loss. Set busy_timeout in both drivers before the first write, and take the cross-process Flock (already used for the models.dev cache race) inside DatabaseMigration.apply, keyed by SQLite's own resolved path so every caller is covered and path aliases collapse. Migration transactions use immediate behavior so a read-to-write upgrade waits on busy_timeout instead of failing at once.
gitRasheed
force-pushed
the
fix/db-migration-race
branch
from
August 24, 2026 00:10
aeffe0b to
7024062
Compare
Default filesystems on Windows and macOS are case-insensitive, so two spellings of one database path must map to one lock. Folding on case-sensitive filesystems too only over-serializes two distinct files for the duration of a migration.
Contributor
Author
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Issue for this PR
Closes #33320
Type of change
What does this PR do?
I reproduced the race by starting six
opencode runprocesses against a fresh global SQLite database. Five failed with "database is locked" within ~15ms.Both drivers enable WAL before setting
busy_timeout, and changing journal mode bypasses the busy handler.DatabaseMigration.applyuses an in-process semaphore around its check and write, so separate processes can run the same migration. Because one shipped migration resets the database, running it twice can silently delete session data. The migration transactions also used DEFERRED mode, which returns SQLITE_BUSY during a read-to-write upgrade without consultingbusy_timeout.I set
busy_timeoutbefore the first write in both drivers and retry WAL conversion within that budget.DatabaseMigration.applynow takes the existing cross-processFlockused bymodels-dev.ts, keyed by the path frompragma_database_listso aliases share a lock. Memory databases skip the lock. Migrations useBEGIN IMMEDIATEand recheck the journal inside the transaction, allowing raced callers to skip completed migrations.#38849 stays open: this covers its startup crashes but not the mid-session ones, which come from a different write path. Its
busy_timeout=0reading is a per-connection artifact; the pragma is set, just bypassed here.How did you verify your code works?
Two cross-process tests in
test/database-migration.test.tsspawn real children on a file barrier: one races four processes at a fresh database, the other rewinds the last migration and asserts the journal ends with exactly one row. They fail unpatched; the manual six-process race went from 1/6 to 6/6 clean. Full core suite and typecheck pass.Screenshots / recordings
Not a UI change.
Checklist