Repository navigation
fix(cache): ignore SQLITE_BUSY in the sqlite cache store - #6049
Merged
Merged
Conversation
The store is meant to be shared between processes. SQLite reports a
database another connection is writing as SQLITE_BUSY/SQLITE_LOCKED
("database is locked"), and those errors currently propagate out of
get(), set(), delete() and the pruning that set() performs, failing the
request that triggered the write.
Caching is best effort, so skip the operation instead of throwing: a
locked database is a cache miss, and the next request retries.
metcoder95
approved these changes
Oct 20, 2026
Codecov Report❌ Patch coverage is
Additional details and impacted files☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merged
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.
What was wrong
SqliteCacheStoreis meant to be shared, but it does not tolerate SQLite'scontention errors. When another connection (another process, or another store
instance in the same process) is writing the same database file, node:sqlite
throws:
That error escapes
get(),set()anddelete(), and it also escapes thepruning that
set()performs, which is what the report in #4124 shows:So a transient lock in a shared cache database fails the request that triggered
the write, instead of just skipping the cache.
Root cause
SQLITE_BUSY(5) /SQLITE_LOCKED(6) are transient: they only mean anotherconnection currently holds the lock. The store has no handling for them, so
every statement is able to throw.
The change
lib/cache/sqlite-cache-store.js:isLockedError()helper that recognises the two transient resultcodes (
errcode5 and 6), so unrelated SQLite errors still propagate.#lockSafe()wrapper that runs a statement and turns a lock errorinto "operation skipped".
the same paths: the lookup in
get(), the lookup/update/insert inset(),delete(), and the pruning statements (#prune()now skips pruning when thesize cannot be read because the database is locked).
A locked database is therefore a cache miss and a skipped write; the next
request retries. This follows the direction of the discussion in #4124
("we should either yield and try again or just ignore the error ... I would
recommend the latter").
Verification
New regression test in
test/cache-interceptor/sqlite-cache-store-tests.js: asecond
DatabaseSyncconnection opens the same file and holds the write lockwith
BEGIN IMMEDIATE, thenset(),get()anddelete()are called whilethe lock is held, and a normal round-trip is checked after
COMMIT.Before the change:
After the change:
Suites:
Fixes #4124
AI assistance (disclosure): this change was prepared with an AI coding agent (Factory Droid) working in my checkout. Every result quoted above comes from actually running the commands shown, not from the agent's description. Happy to answer questions or rework anything.