fix: serialize SQLite operations and protect connection lifetime - #326
Merged
chrispader merged 2 commits intoSep 10, 2026
Merged
Conversation
chrispader
added this pull request to stack #327
September 10, 2026 13:41
chrispader
force-pushed
the
codex/sqlite-operation-serialization
branch
from
September 10, 2026 14:06
523fdfd to
6c12737
Compare
chrispader
removed this pull request from stack #327
September 10, 2026 14:09
chrispader
changed the base branch from
codex/configurable-sqlite-threading
to
codex/configurable-sqlite-performance
September 10, 2026 14:10
chrispader
added this pull request to stack #329
September 10, 2026 14:10
chrispader
force-pushed
the
codex/sqlite-operation-serialization
branch
from
September 10, 2026 16:04
6c12737 to
0da6c3d
Compare
chrispader
removed this pull request from stack #329
September 10, 2026 16:07
chrispader
force-pushed
the
codex/sqlite-operation-serialization
branch
from
September 10, 2026 16:10
0da6c3d to
52e5bd8
Compare
chrispader
added this pull request to stack #331
September 10, 2026 16:12
This was referenced Sep 10, 2026
Closed
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.
Async statements could run concurrently against the same SQLite handle, and ordinary queries were not coordinated with the transaction queue. That allowed an external write to become part of another caller's transaction and roll back with it. The native connection registry also exposed raw handles to close/reopen races, including result metadata being read after another statement changed the connection state.
This PR is stacked on #328, which adds independent performance-mode configuration, and #328 is stacked on #325, which restores SQLite mutexes by default. Merge the stack in that order. This addresses the operation scheduling and connection lifetime problems in #303, #304, and #312.
Solution
This change routes session async operations through one FIFO queue per database and rejects conflicting sync operations with the existing busy error. Transaction callbacks keep explicit ownership through their
txobject, so their internal statements bypass the public queue without recursively waiting on themselves. Session close now refuses pending work, while attach, detach, batch, file import, and delete follow the same admission rules.The native registry now stores shared connection state behind lifecycle and registry locks. Each connection lock covers the full statement, row conversion, metadata, change count, and insert ID sequence. Async jobs capture that connection before entering the worker pool, so close/reopen cannot redirect old work to a replacement handle. Batch and file import hold the same lock for their whole transaction.
This branch preserves Tr岷 膼矛nh Huy's original-author commit from #319, then adds synchronization around its duplicate-open guard and failed-open cleanup. If #319 merges separately first, this branch can be rebased to omit that commit.
Inside a transaction callback, callers must use the passed
txobject, including from helper functions. Awaiting a same-database session/global queued operation from that callback creates a circular wait because the operation waits for the transaction and the transaction waits for the operation. This was already possible withexecuteBatchAsync; ordinary async execution now follows the same ownership rule.Native calls are serialized per connection, not across all database handles.
SQLITE_THREADSAFE=0therefore remains unsafe when separate databases run concurrently unless the caller globally serializes every SQLite call.Reproduction
On iOS with
SQLITE_THREADSAFE=1and #319 applied, the new rollback-isolation regression fails before this change: the external row disappears with the transaction. It passes after the queue fix. All 80 committed Harness tests passed on iOS and Android, along with a temporary runtime probe that checksTHREADSAFE=1and completes 100 parallel reads.