Skip to content

[Due for payment 2026-06-11] [Onyx] Fix dead-code async-catch in tryOrDegradePerformance #90632

Closed

Description

@fabioh8010

Coming from #87862 (comment).

Issue

The tryOrDegradePerformance function in lib/storage/index.ts:31-55 wraps every storage operation with a try/catch intended to detect known critical errors and trigger a graceful fallback via degradePerformance. However, the try/catch only catches synchronous throws — and every storage operation in Onyx is asynchronous, so the fallback path is effectively dead code.

function tryOrDegradePerformance<T>(fn: () => Promise<T> | T, waitForInitialization = true): Promise<T> {
    return new Promise<T>((resolve, reject) => {
        const promise = waitForInitialization ? initPromise : Promise.resolve();

        promise.then(() => {
            try {
                resolve(fn());                     // fn() returns a Promise — rejection escapes
            } catch (error) {
                // This catch only fires on synchronous throws.
                // IDB operations reject asynchronously — this block never executes.
                if (error instanceof Error) {
                    if (error.message.includes('Internal error opening backing store for indexedDB.open')) {
                        degradePerformance(error);
                    }
                    if (error.message.includes('IDBKeyVal store could not be created')) {
                        degradePerformance(error);
                    }
                }
                reject(error);
            }
        });
    });
}

Because resolve(fn()) schedules the resolution of the outer Promise with the inner Promise's eventual value rather than awaiting it, any rejection from fn() (e.g. Storage.setItem, Storage.multiSet) is propagated through the Promise chain — it never re-enters the catch block. The intended degradation logic is unreachable.

Notably, the existing string check for 'Internal error opening backing store for indexedDB.open' was meant to handle the exact error analyzed in #87862, but it has never fired in production for this reason.

Solution

Convert the synchronous try/catch into a Promise .catch(...) so async rejections are actually caught:

function tryOrDegradePerformance<T>(fn: () => Promise<T> | T, waitForInitialization = true): Promise<T> {
    const promise = waitForInitialization ? initPromise : Promise.resolve();
    return promise.then(() =>
        Promise.resolve(fn()).catch((error) => {
            if (error instanceof Error && error.message.includes('IDBKeyVal store could not be created')) {
                degradePerformance(error);
            }
            return Promise.reject(error);
        }),
    );
}

While making this change, remove the 'Internal error opening backing store for indexedDB.open' string check from this function. Per the parent comment, this error class is permanent corruption and won't recover from a provider swap — it gets routed to a dedicated heal path (separate action item #90636) instead of degradePerformance. Leaving the check in would conflict with that heal mechanism.

The other branch ('IDBKeyVal store could not be created') can stay as-is for now — it's a different class (init failure), and routing it to degradePerformance is still the intended behavior.

Test plan

  • Add a unit test that confirms a Promise rejection from fn() (e.g. a mocked Storage.setItem that rejects with new Error('IDBKeyVal store could not be created')) now triggers degradePerformance.
  • Confirm existing storage tests still pass.
  • Verify in production logs that the Logger.logHmmm("Error while using ${provider.name}. Falling back to only using cache and dropping storage.") message starts appearing where appropriate (currently appears 0 times).
Issue OwnerCurrent Issue Owner: @elirangoshen

Activity

  1. elirangoshen commented on May 14, 2026

    @elirangoshen
    Contributor

    Hi, Im Eliran from callstack, I can work on this issue .

  2. elirangoshen commented on May 14, 2026

    @elirangoshen
    Contributor

    Update: I opened pr , and working to address potential comments

  3. elirangoshen commented on May 18, 2026

    @elirangoshen
    Contributor

    Update: I addressed comments in my pr and waiting for feedback

  4. Julesssss commented on May 19, 2026

    @Julesssss
    Contributor

    RN PR merged, awaiting bump, we have quite a long queue currently.

  5. melvin-bot commented on May 21, 2026

    @melvin-bot

    @elirangoshen Whoops! This issue is 2 days overdue. Let's get this updated quick!

  6. elirangoshen commented on May 22, 2026

    @elirangoshen
    Contributor

    It merged already

  7. 1 remaining item

  8. melvin-bot commented on May 25, 2026

    @melvin-bot

    @elirangoshen Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

  9. elirangoshen commented on May 26, 2026

    @elirangoshen
    Contributor

    we wait for onyx bump, as the pr got merged

  10. melvin-bot commented on May 29, 2026

    @melvin-bot

    @elirangoshen Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

  11. elirangoshen commented on Jun 1, 2026

    @elirangoshen
    Contributor

    Waiting for onyx bump

  12. changed the title [-][Onyx] Fix dead-code async-catch in tryOrDegradePerformance[/-] [+][Due for payment 2026-06-11] [Onyx] Fix dead-code async-catch in tryOrDegradePerformance[/+] on Jun 4, 2026
  13. melvin-bot commented on Jun 4, 2026

    @melvin-bot

    The solution for this issue has been 🚀 deployed to production 🚀 in version 9.3.98-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:

    If no regressions arise, payment will be issued on 2026-06-11. 🎊

    For reference, here are some details about the assignees on this issue:

  14. cristipaval commented on Jun 4, 2026

    @cristipaval
    Contributor

    No payment needed here. Closing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions