Skip to content

Commit

Permalink
fix(offline): Clean up orphaned segments on abort (shaka-project#4177)
Browse files Browse the repository at this point in the history
If a storage operation is aborted (via API, not via closing the page),
this will now be cleaned up from the database.  More work is needed to
find and remove orphaned segments in the database.

Related to shaka-project#4166 and follow-up to PR shaka-project#4176.
  • Loading branch information
joeyparrish authored Apr 30, 2022
1 parent c1c9613 commit c07447f
Showing 1 changed file with 56 additions and 58 deletions.
114 changes: 56 additions & 58 deletions lib/offline/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -485,42 +485,54 @@ shaka.offline.Storage = class {

const usingBgFetch = false; // TODO: Get.

if (this.getManifestIsEncrypted_(manifest) && usingBgFetch &&
!this.getManifestIncludesInitData_(manifest)) {
// Background fetch can't make DRM sessions, so if we have to get the
// init data from the init segments, download those first before anything
// else.
await download(toDownload.filter((info) => info.isInitSegment), true);
this.ensureNotDestroyed_();
toDownload = toDownload.filter((info) => !info.isInitSegment);

// Copy these and reset them now, before calling await.
const manifestUpdates = pendingManifestUpdates;
const dataSize = pendingDataSize;
pendingManifestUpdates = {};
pendingDataSize = 0;

manifestDB =
(await shaka.offline.Storage.assignSegmentsToManifest(
manifestId, manifestUpdates, dataSize,
() => this.ensureNotDestroyed_())) || manifestDB;
this.ensureNotDestroyed_();
}
try {
if (this.getManifestIsEncrypted_(manifest) && usingBgFetch &&
!this.getManifestIncludesInitData_(manifest)) {
// Background fetch can't make DRM sessions, so if we have to get the
// init data from the init segments, download those first before
// anything else.
await download(toDownload.filter((info) => info.isInitSegment), true);
this.ensureNotDestroyed_();
toDownload = toDownload.filter((info) => !info.isInitSegment);

if (!usingBgFetch) {
await download(toDownload, false);
this.ensureNotDestroyed_();
// Copy these and reset them now, before calling await.
const manifestUpdates = pendingManifestUpdates;
const dataSize = pendingDataSize;
pendingManifestUpdates = {};
pendingDataSize = 0;

manifestDB =
(await shaka.offline.Storage.assignSegmentsToManifest(
manifestId, pendingManifestUpdates, pendingDataSize,
() => this.ensureNotDestroyed_())) || manifestDB;
this.ensureNotDestroyed_();
await shaka.offline.Storage.assignSegmentsToManifest(
storage, manifestId, manifestDB, manifestUpdates, dataSize,
() => this.ensureNotDestroyed_());
this.ensureNotDestroyed_();
}

goog.asserts.assert(
!manifestDB.isIncomplete, 'The manifest should be complete by now');
} else {
// TODO: Send the request to the service worker. Don't await the result.
if (!usingBgFetch) {
await download(toDownload, false);
this.ensureNotDestroyed_();

// Copy these and reset them now, before calling await.
const manifestUpdates = pendingManifestUpdates;
const dataSize = pendingDataSize;
pendingManifestUpdates = {};
pendingDataSize = 0;

await shaka.offline.Storage.assignSegmentsToManifest(
storage, manifestId, manifestDB, manifestUpdates, dataSize,
() => this.ensureNotDestroyed_());
this.ensureNotDestroyed_();

goog.asserts.assert(
!manifestDB.isIncomplete, 'The manifest should be complete by now');
} else {
// TODO: Send the request to the service worker. Don't await the result.
}
} catch (error) {
const dataKeys = Object.values(pendingManifestUpdates);
// Remove these pending segments that are not yet linked to the manifest.
await storage.removeSegments(dataKeys, (key) => {});

throw error;
}
}

Expand All @@ -544,38 +556,27 @@ shaka.offline.Storage = class {
}

/**
* Load the given manifest, assigns database key to all the segments, then
* stores the updated manifest.
* Updates the given manifest, assigns database keys to segments, then stores
* the updated manifest.
*
* It is up to the caller to ensure that this method is not called
* concurrently on the same manifest.
*
* @param {shaka.extern.StorageCell} storage
* @param {number} manifestId
* @param {!shaka.extern.ManifestDB} manifestDB
* @param {!Object.<string, number>} manifestUpdates
* @param {number} dataSizeUpdate
* @param {function()} throwIfAbortedFn A function that should throw if the
* download has been aborted.
* @return {!Promise.<?shaka.extern.ManifestDB>}
* @return {!Promise}
*/
static async assignSegmentsToManifest(
manifestId, manifestUpdates, dataSizeUpdate, throwIfAbortedFn) {
/** @type {shaka.offline.StorageMuxer} */
const muxer = new shaka.offline.StorageMuxer();

storage, manifestId, manifestDB, manifestUpdates, dataSizeUpdate,
throwIfAbortedFn) {
let manifestUpdated = false;
let activeHandle;
/** @type {!shaka.extern.ManifestDB} */
let manifestDB;

try {
await muxer.init();
activeHandle = await muxer.getActive();

// Load the manifest.
const manifests = await activeHandle.cell.getManifests([manifestId]);
throwIfAbortedFn();
manifestDB = manifests[0];

// Assign the stored data to the manifest.
let complete = true;
for (const stream of manifestDB.streams) {
Expand Down Expand Up @@ -616,26 +617,23 @@ shaka.offline.Storage = class {
manifestDB.isIncomplete = false;
}

// Re-store the manifest.
await activeHandle.cell.updateManifest(manifestId, manifestDB);
// Update the manifest.
await storage.updateManifest(manifestId, manifestDB);
manifestUpdated = true;
throwIfAbortedFn();
} catch (e) {
await shaka.offline.Storage.cleanStoredManifest(manifestId);

if (activeHandle && !manifestUpdated) {
if (!manifestUpdated) {
const dataKeys = Object.values(manifestUpdates);
// The cleanStoredManifest method will not "see" any segments that have
// been downloaded but not assigned to the manifest yet. So un-store
// them separately.
await activeHandle.cell.removeSegments(dataKeys, (key) => {});
await storage.removeSegments(dataKeys, (key) => {});
}

throw e;
} finally {
await muxer.destroy();
}
return manifestDB;
}

/**
Expand Down

0 comments on commit c07447f

Please sign in to comment.