fix: add IDB backing store corruption healing mechanism#780
Open
leshniak wants to merge 3 commits into
Open
Conversation
Adds a Dexie-style heal pattern to createStore for Chromium's Internal error opening backing store error (884K errors/month). - isBackingStoreError() detects the Chromium-specific corruption - Shared healAttemptsRemaining counter (3, reset on success) - On backing store error: clear cached connection, retry once - Clear dbp on rejection so retries get fresh indexedDB.open() - 5 new tests: mid-session heal, init heal, budget exhaustion, budget reset, error classification No deleteDatabase(), no provider swap, no UI changes. Scoped to IDBKeyValProvider only -- SQLite provider untouched. Ref: Expensify/App#90636 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bd7a14f to
32a6cc8
Compare
- Capture dbp reference before attaching reject handler; only clear if dbp hasn't been replaced by a concurrent heal/retry (prevents stale rejection handler from clearing a newer promise) - Add comment documenting concurrent store() budget drain behavior - Fix test formatting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ose) The heal path clears the cached dbp and reopens via indexedDB.open(), but does not call db.close() on the old IDBDatabase. Updated comments and log messages from 'close + reopen' to 'drop cached connection and reopen' to match what the code actually does. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.
Details
Adds an IDB healing mechanism for Chromium's
UnknownError: Internal error opening backing store for indexedDB.open.— 884K errors/month, 26.3% of all storage errors (investigation, solution design).This is a Dexie-style heal pattern (
PR1398_maxLoop) insidecreateStore.ts— the IDB connection manager forIDBKeyValProvider.What it does:
isBackingStoreError()— detects the Chromium-specific corruption error (DOMExceptionwithname === 'UnknownError'and message containing'Internal error opening backing store')healAttemptsRemainingcounter (initialized to 3, reset on every successful IDB operation)dbp, retryexecuteTransactiononce (forces a freshindexedDB.open())dbp(capture reference before attaching, only clear if unchanged)dbpon rejection ingetDB()andverifyStoreExists(fixes pre-existing bug where a cached rejected promise caused infinite failures)What it does NOT do (by design per #90636):
deleteDatabase()— proven to also fail when LevelDB files are corruptMemoryOnlyProviderdegradation — cache already absorbs all writes during the sessionstorage/index.tsorOnyxUtils.ts— those are separate issues (#90632, #90633)Related Issues
Expensify/App#90636
Expensify/App#87862
Automated Tests
5 new tests in
tests/unit/storage/providers/createStoreTest.ts:db.transaction()throws backing store error once, heals via dropping cached connection and reopeningindexedDB.open()rejects twice, third succeeds across twostore()callsUnknownErrorwith wrong message andQuotaExceededErrorboth bypass heal pathAll 440 tests pass.
Manual Tests
npm run typecheckpassesnpm run lintpassesnpm testpasses (440/440)IDB heallog lines — fraction of users that emit them and then continue without further storage errors indicates heal success rateAuthor Checklist
### Related Issuessection aboveTestssectiontoggleReportand notonIconClick)myBool && <MyComponent />.STYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)/** comment above it */thisproperly so there are no scoping issues (i.e. foronClick={this.submit}the methodthis.submitshould be bound tothisin the constructor)thisare necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);ifthis.submitis never passed to a component event handler likeonClick)Avataris modified, I verified thatAvataris working as expected in all cases)mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
N/A — library-level change, IDB is web-only. No UI, no native code touched.
Android: mWeb Chrome
N/A — library-level change, IDB is web-only. No UI, no native code touched.
iOS: Native
N/A — library-level change, IDB is web-only. No UI, no native code touched.
iOS: mWeb Safari
N/A — library-level change, IDB is web-only. No UI, no native code touched.
MacOS: Chrome / Safari
N/A — library-level change, no UI. Verified via 440/440 unit tests passing.