From 7952c3d61da343b285819417003db0442a8e571b Mon Sep 17 00:00:00 2001 From: Eduardo Fungairino Date: Tue, 8 Oct 2024 13:54:39 -0400 Subject: [PATCH] Add idb error handling for Internal error opening backing store (#9252) * Add idb error handling for Internal error opening backing store * knip * pr feedback * update comments --- src/utils/idbUtils.test.ts | 24 ++++++++++++++++++++++++ src/utils/idbUtils.ts | 33 +++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/utils/idbUtils.test.ts b/src/utils/idbUtils.test.ts index 2ee90af7e5..eed788242d 100644 --- a/src/utils/idbUtils.test.ts +++ b/src/utils/idbUtils.test.ts @@ -127,4 +127,28 @@ describe("withIdbErrorHandling", () => { expect(deleteDB).toHaveBeenCalledWith(databaseName, expect.anything()); }, 10_000); // Increased timeout due to default backoffs in p-retry + + it("should delete the database if it fails all retries with Internal error opening backing store", async () => { + const onFailedAttemptMock = jest.fn(); + mockOpenIDB.mockRejectedValue( + new DOMException( + "Internal error opening backing store for indexedDB.open.", + ), + ); + + await expect( + withIdbErrorHandling(mockOpenIDB, databaseName)(mockDbOperation, { + operationName, + shouldRetry: () => true, + onFailedAttempt: onFailedAttemptMock, + }), + ).rejects.toThrow( + "Internal error opening backing store for indexedDB.open.", + ); + + expect(mockOpenIDB).toHaveBeenCalledTimes(4); // 1 initial + 3 retries + expect(onFailedAttemptMock).toHaveBeenCalledTimes(4); + + expect(deleteDB).toHaveBeenCalledWith(databaseName, expect.anything()); + }, 10_000); // Increased timeout due to default backoffs in p-retry }); diff --git a/src/utils/idbUtils.ts b/src/utils/idbUtils.ts index af91d7591f..7b14305d43 100644 --- a/src/utils/idbUtils.ts +++ b/src/utils/idbUtils.ts @@ -130,6 +130,7 @@ export function isIDBQuotaError(error: unknown): boolean { * Before Chrome 130, there is no way to determine if the file is missing or if some other error occurred. * In Chrome 130 and later, the error message remains the same, the type of error is different. * NotFoundError if the file is missing, DataError for any other error. + * * @see https://chromestatus.com/feature/5140210640486400 * @param error the error object */ @@ -138,6 +139,20 @@ export function isIDBLargeValueError(error: unknown): boolean { return message.includes("Failed to read large IndexedDB value"); } +/** + * Error occurring due to corrupt chrome profile but unclear how this happens. + * This error seems to be unrecoverable and the only solution is to delete the database. + * https://jasonsavard.com/forum/discussion/4233/unknownerror-internal-error-opening-backing-store-for-indexeddb-open + * + * @param error the error object + */ +function isIDBErrorOpeningBackingStore(error: unknown): boolean { + const message = getErrorMessage(error); + return message.includes( + "Internal error opening backing store for indexedDB.open", + ); +} + /** * The large value error could be a NotFoundError or a DataError. * NotFoundError may be fixable on a case-by-case basis. @@ -227,15 +242,17 @@ export const withIdbErrorHandling = }, ); } catch (error) { - /** - * Any retries have failed by this point - * An error for a single value can break bulk operations on the whole DB - * We don't know of a way to drop the single bad record even if we know which one it is - * So we delete the database - */ - if (isIDBLargeValueError(error)) { + if ( + // Large IndexedDB value error for a single DB entry can break bulk operations on the whole DB, + // and we don't know of a way to drop the single bad record even if we know which one it is, + // so we need to delete the whole database. + isIDBLargeValueError(error) || + // "Internal error opening backing store for indexedDB.open" is an unrecoverable error that + // requires deleting the database. + isIDBErrorOpeningBackingStore(error) + ) { console.error( - `Deleting ${databaseName} database due to permanent IndexDB large value error.`, + `Deleting ${databaseName} database due to unrecoverable IndexDB error.`, ); await deleteDatabase(databaseName); }