Skip to content

Commit

Permalink
fix(offline): Add storage muxer init timeout (shaka-project#4566)
Browse files Browse the repository at this point in the history
In some cases, indexedDB.open() can end up calling neither callback.
When this does happen, according to my initial testing, it happens
consistently when reloading the page, so it's not a one-off fluke but
presumably some sort of implementation or browser install problem. If
that does happen, the init promise of the storage muxer hangs forever,
potentially blocking other operations from happening. This adds a
timeout to the invocation of indexedDB.open(), after which the operation
fails with a new error.
  • Loading branch information
theodab authored Oct 13, 2022
1 parent 62906bd commit d4d3740
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 0 deletions.
24 changes: 24 additions & 0 deletions lib/offline/indexeddb/storage_mechanism.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ goog.require('shaka.offline.indexeddb.V5StorageCell');
goog.require('shaka.util.Error');
goog.require('shaka.util.PublicPromise');
goog.require('shaka.util.Platform');
goog.require('shaka.util.Timer');


/**
Expand Down Expand Up @@ -55,8 +56,25 @@ shaka.offline.indexeddb.StorageMechanism = class {
const version = shaka.offline.indexeddb.StorageMechanism.VERSION;

const p = new shaka.util.PublicPromise();

// Add a timeout mechanism, for the (rare?) case where no callbacks are
// called at all, so that this method doesn't hang forever.
let timedOut = false;
const timeOutTimer = new shaka.util.Timer(() => {
timedOut = true;
p.reject(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.STORAGE,
shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
});
timeOutTimer.tickAfter(2);

const open = window.indexedDB.open(name, version);
open.onsuccess = (event) => {
if (timedOut) {
// Too late, we have already given up on opening the storage mechanism.
return;
}
const db = open.result;
this.db_ = db;
this.v1_ = shaka.offline.indexeddb.StorageMechanism.createV1_(db);
Expand All @@ -68,18 +86,24 @@ shaka.offline.indexeddb.StorageMechanism = class {
this.v5_ = shaka.offline.indexeddb.StorageMechanism.createV5_(db);
this.sessions_ =
shaka.offline.indexeddb.StorageMechanism.createEmeSessionCell_(db);
timeOutTimer.stop();
p.resolve();
};
open.onupgradeneeded = (event) => {
// Add object stores for the latest version only.
this.createStores_(open.result);
};
open.onerror = (event) => {
if (timedOut) {
// Too late, we have already given up on opening the storage mechanism.
return;
}
p.reject(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.STORAGE,
shaka.util.Error.Code.INDEXED_DB_ERROR,
open.error));
timeOutTimer.stop();

// Firefox will raise an error on the main thread unless we stop it here.
event.preventDefault();
Expand Down
7 changes: 7 additions & 0 deletions lib/util/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,13 @@ shaka.util.Error.Code = {
*/
'MODIFY_OPERATION_NOT_SUPPORTED': 9016,

/**
* When attempting to open an indexedDB instance, nothing happened for long
* enough for us to time out. This keeps the storage mechanism from hanging
* indefinitely, if neither the success nor error callbacks are called.
*/
'INDEXED_DB_INIT_TIMED_OUT': 9017,

/**
* CS IMA SDK, required for ad insertion, has not been included on the page.
*/
Expand Down
30 changes: 30 additions & 0 deletions test/offline/storage_integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,36 @@ filterDescribe('Storage', storageSupport, () => {
}
});

/**
* In some situations, indexedDB.open() can just hang, and call neither the
* 'success' nor the 'error' callbacks.
* I'm not sure what causes it, but it seems to happen consistently between
* reloads when it does so it might be a browser-based issue.
* In that case, we should time out with an error, instead of also hanging.
*/
it('throws an error if indexedDB open times out', async () => {
const oldOpen = window.indexedDB.open;
window.indexedDB.open = () => {
// Just return a dummy object.
return /** @type {!IDBOpenDBRequest} */ ({
onsuccess: (event) => {},
onerror: (error) => {},
});
};

/** @type {!shaka.offline.StorageMuxer} */
const muxer = new shaka.offline.StorageMuxer();
const expectedError = shaka.test.Util.jasmineError(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
shaka.util.Error.Category.STORAGE,
shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));

await expectAsync(muxer.init())
.toBeRejectedWith(expectedError);

window.indexedDB.open = oldOpen;
});

it('throws an error if the content is a live stream', async () => {
const expected = Util.jasmineError(new shaka.util.Error(
shaka.util.Error.Severity.CRITICAL,
Expand Down

0 comments on commit d4d3740

Please sign in to comment.