Skip to content

fix: IDB backing store corruption healing (onyx PR #780 test)#91085

Draft
leshniak wants to merge 5 commits into
Expensify:mainfrom
callstack-internal:fix/idb-corruption-heal-test
Draft

fix: IDB backing store corruption healing (onyx PR #780 test)#91085
leshniak wants to merge 5 commits into
Expensify:mainfrom
callstack-internal:fix/idb-corruption-heal-test

Conversation

@leshniak
Copy link
Copy Markdown
Contributor

@leshniak leshniak commented May 19, 2026

Explanation of Change

Pins react-native-onyx to PR #780 HEAD (92405996) which adds an IDB backing store corruption healing mechanism. This is a test-only PR to validate the fix in Expensify/App context.

What PR #780 does:

  • Detects Chromium UnknownError: Internal error opening backing store (884K errors/month, 26.3% of all storage errors)
  • Detects Safari/WebKit connection lost and connection closing errors (637K errors/month, 19%)
  • Implements Dexie-style heal: drop cached IDB connection, reopen with fresh indexedDB.open(), budgeted to 3 attempts (reset on success)
  • Full diagnostic logging via Logger.logAlert/logInfo at every step
  • No deleteDatabase(), no provider swap, no UI changes

Also removes stale react-native-onyx+3.0.71.patch — the changes it patched (reverting PR #770) already landed upstream in onyx 3.0.72 via PR #785.

Fixed Issues

$ #90636
PROPOSAL:

Tests

Each test injects a monkey-patch via the browser console to simulate the error class, then triggers an Onyx write (e.g. navigate to a different chat). No code changes needed.

Test 1 — Backing store error (Chromium path):

  1. Open the app in Chrome, log in, navigate to any chat
  2. Open DevTools Console, paste:
    const _origTx = IDBDatabase.prototype.transaction;
    let _fired = false;
    IDBDatabase.prototype.transaction = function(...args) {
      if (!_fired) {
        _fired = true;
        IDBDatabase.prototype.transaction = _origTx;
        throw new DOMException('Internal error opening backing store', 'UnknownError');
      }
      return _origTx.apply(this, args);
    };
  3. Navigate to a different chat (triggers Onyx write)
  4. Filter console with regex: /IDB (heal|InvalidStateError|error|connection closed|Store \w+ does not exist|Creating store)/
  5. Expected log sequence (via Log.ts):
    • IDB heal: backing store error detected — dropping cached connection and reopening (2 attempts left)
    • IDB heal: successfully recovered after backing store error
  6. ✅ App recovers — navigation works, no white screen, subsequent operations succeed

Test 2 — Connection lost (Safari path):

  1. Same setup as Test 1
  2. Paste in DevTools Console:
    const _origTx = IDBDatabase.prototype.transaction;
    let _fired = false;
    IDBDatabase.prototype.transaction = function(...args) {
      if (!_fired) {
        _fired = true;
        IDBDatabase.prototype.transaction = _origTx;
        throw new DOMException('Connection to Indexed Database server lost', 'UnknownError');
      }
      return _origTx.apply(this, args);
    };
  3. Navigate to a different chat
  4. Expected log sequence:
    • IDB heal: connection lost error detected — dropping cached connection and reopening (2 attempts left)
    • IDB heal: successfully recovered after connection lost error
  5. ✅ App recovers seamlessly

Test 3 — Budget exhaustion (3 consecutive failures, then gives up):

  1. Same setup as Test 1
  2. Paste in DevTools Console:
    const _origTx = IDBDatabase.prototype.transaction;
    let _count = 0;
    IDBDatabase.prototype.transaction = function(...args) {
      if (_count < 6) {
        _count++;
        throw new DOMException('Internal error opening backing store', 'UnknownError');
      }
      IDBDatabase.prototype.transaction = _origTx;
      return _origTx.apply(this, args);
    };
  3. Navigate to a different chat
  4. Expected log sequence:
    • IDB heal: backing store error detected — dropping cached connection and reopening (2 attempts left)
    • IDB heal: backing store error detected — dropping cached connection and reopening (1 attempts left)
    • IDB heal: backing store error detected — dropping cached connection and reopening (0 attempts left)
    • IDB heal: backing store error — heal budget exhausted, giving up
  5. ✅ Onyx's setWithRetry takes over (up to 5 retries). App may degrade but should not crash or white-screen
  • Verify that no errors appear in the JS console

Screenshots/Videos

Web - Chrome (Test 1: backing store heal)

TODO: attach recording

Web - Chrome (Test 3: budget exhaustion)

TODO: attach recording

Offline tests

N/A — this change targets IDB connection/corruption errors, not network state. Onyx's existing offline handling is unchanged.

QA Steps

Same as Tests section above.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).

Pin onyx to Expensify/react-native-onyx#780 (commit beb75d5f) which adds
IDB backing store corruption healing. Remove stale 3.0.71 patch (already
reverted upstream in 3.0.72 via PR Expensify#785).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ This PR is possibly changing native code and/or updating libraries, it may cause problems with HybridApp. Please check if any patch updates are required in the HybridApp repo and run an AdHoc build to verify that HybridApp will not break. Ask Contributor Plus for help if you are not sure how to handle this. ⚠️

leshniak and others added 4 commits May 20, 2026 13:22
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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