Skip to content
This repository has been archived by the owner on May 26, 2022. It is now read-only.

Commit

Permalink
fix: Try to detect masked QuotaExceededErrors and re-throw them
Browse files Browse the repository at this point in the history
  • Loading branch information
birtles committed Feb 27, 2021
1 parent e4fce37 commit 34380bf
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 22 deletions.
13 changes: 13 additions & 0 deletions src/quota-exceeded-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class QuotaExceededError extends Error {
constructor(...params: any[]) {
super(...params);
Object.setPrototypeOf(this, QuotaExceededError.prototype);

if (typeof (Error as any).captureStackTrace === 'function') {
(Error as any).captureStackTrace(this, QuotaExceededError);
}

this.name = 'QuotaExceededError';
this.message = 'The current transaction exceeded its quota limitations.';
}
}
63 changes: 41 additions & 22 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {

import { DataSeries } from './data-series';
import { DataVersion } from './data-version';
import { stripFields } from './utils';
import { QuotaExceededError } from './quota-exceeded-error';
import {
getIdForKanjiRecord,
getIdForNameRecord,
Expand All @@ -24,6 +24,7 @@ import {
toWordRecord,
WordRecord,
} from './records';
import { stripFields } from './utils';

interface DataVersionRecord extends DataVersion {
id: 1 | 2 | 3 | 4;
Expand Down Expand Up @@ -348,9 +349,7 @@ export class JpdictStore {
// See: https://jsfiddle.net/birtles/vx4urLkw/17/
const putPromise = targetTable.put(record);

// Add some extra logging for puts because we occasionally encounter
// a situation where we get very generic errors like `Error:
// undefined` and we'd like to understand better what is going on.
// Add some extra logging directly on the put promise.
putPromise.catch((e) => {
console.log('Got error putting record');
console.log(e, e?.name, e?.message);
Expand Down Expand Up @@ -388,24 +387,13 @@ export class JpdictStore {
// Error: undefined
//
// We _think_ this happens in some cases where the disk space quota is
// exceeded, and then something else goes wrong (e.g. QuotaManager finds
// unexpected files in the idb folder). For now, we are simply trying to
// find a reliable way to detect this so the following adds various
// logging that hopefully sheds some light on this case.
if (
typeof e === 'undefined' ||
(e instanceof Error &&
(typeof e.name === 'undefined' || e.name === 'undefined'))
) {
console.log('Got undefined error');
try {
const estimate = await self.navigator.storage.estimate();
console.log('Storage estimate');
console.log(JSON.stringify(estimate));
} catch (e) {
console.log('Got error querying the storage quota');
console.log(e);
}
// exceeded so we try to detect that case and throw an actual
// QuotaExceededError instead.
if (isVeryGenericError(e) && (await atOrNearQuota())) {
console.log(
'Detected generic error masking a quota exceeded situation'
);
throw new QuotaExceededError();
}

throw e;
Expand Down Expand Up @@ -458,3 +446,34 @@ export class JpdictStore {
return result;
}
}

// We occasionally get these obscure errors when running IndexedDB in an
// extension context where the error returned serializes as simply:
//
// Error: undefined
//
// Our current theory is that it occurs when we hit an out-of-quota situation.
function isVeryGenericError(e: any): boolean {
if (typeof e === 'undefined') {
return true;
}

// Look for an Error without a name or an object with name 'Error' but no
// message
return (
(e instanceof Error && !e?.name) || (e?.name === 'Error' && !e?.message)
);
}

async function atOrNearQuota(): Promise<boolean> {
try {
const estimate = await self.navigator.storage.estimate();
return (
typeof estimate.usage !== 'undefined' &&
typeof estimate.quota !== 'undefined' &&
estimate.usage / estimate.quota > 0.9
);
} catch (_e) {
return false;
}
}

0 comments on commit 34380bf

Please sign in to comment.