Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(offline): Clean up orphaned segments on abort #4177

Merged
merged 2 commits into from
Apr 30, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc needs to be updated again, now that this no longer loads the manifest.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is accurate. I added the parameter. Before, it was loaded inside the method. Now, it is provided by the caller, who already had a copy. The caller's copy is updated in-place as well as stored in the DB.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wait, I see what you mean now. Yes, you're right. I'll take care of that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish Github let me comment on lines that the PR didn't change. It'd make this kind of thing so much clearer.

* @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