Fix DownloadManager assertion failure

Issue: #8419
PiperOrigin-RevId: 350134719
This commit is contained in:
olly 2021-01-05 15:18:28 +00:00 committed by Ian Baker
parent ca287ba562
commit edc5b83929
3 changed files with 81 additions and 17 deletions

View file

@ -12,6 +12,10 @@
would not be dismissed when tapping outside of the menu area or pressing would not be dismissed when tapping outside of the menu area or pressing
the back button, on API level 22 and earlier the back button, on API level 22 and earlier
([#8272](https://github.com/google/ExoPlayer/issues/8272)). ([#8272](https://github.com/google/ExoPlayer/issues/8272)).
* Downloads:
* Fix crash in `DownloadManager` that could occur when adding a stopped
download with the same ID as a download currently being removed
([#8419](https://github.com/google/ExoPlayer/issues/8419)).
* IMA extension: * IMA extension:
* Fix a condition where playback can get stuck before an empty ad * Fix a condition where playback can get stuck before an empty ad
([#8205](https://github.com/google/ExoPlayer/issues/8205)). ([#8205](https://github.com/google/ExoPlayer/issues/8205)).
@ -147,7 +151,7 @@
([#7378](https://github.com/google/ExoPlayer/issues/7378)). ([#7378](https://github.com/google/ExoPlayer/issues/7378)).
* Downloads: Fix issue retrying progressive downloads, which could also result * Downloads: Fix issue retrying progressive downloads, which could also result
in a crash in `DownloadManager.InternalHandler.onContentLengthChanged` in a crash in `DownloadManager.InternalHandler.onContentLengthChanged`
([#8078](https://github.com/google/ExoPlayer/issues/8078). ([#8078](https://github.com/google/ExoPlayer/issues/8078)).
* HLS: Fix crash affecting chunkful preparation of master playlists that start * HLS: Fix crash affecting chunkful preparation of master playlists that start
with an I-FRAME only variant with an I-FRAME only variant
([#8025](https://github.com/google/ExoPlayer/issues/8025)). ([#8025](https://github.com/google/ExoPlayer/issues/8025)).

View file

@ -856,7 +856,7 @@ public final class DownloadManager {
private void setStopReason(Download download, int stopReason) { private void setStopReason(Download download, int stopReason) {
if (stopReason == STOP_REASON_NONE) { if (stopReason == STOP_REASON_NONE) {
if (download.state == STATE_STOPPED) { if (download.state == STATE_STOPPED) {
putDownloadWithState(download, STATE_QUEUED); putDownloadWithState(download, STATE_QUEUED, STOP_REASON_NONE);
} }
} else if (stopReason != download.stopReason) { } else if (stopReason != download.stopReason) {
@Download.State int state = download.state; @Download.State int state = download.state;
@ -910,7 +910,7 @@ public final class DownloadManager {
Log.e(TAG, "Failed to remove nonexistent download: " + id); Log.e(TAG, "Failed to remove nonexistent download: " + id);
return; return;
} }
putDownloadWithState(download, STATE_REMOVING); putDownloadWithState(download, STATE_REMOVING, STOP_REASON_NONE);
syncTasks(); syncTasks();
} }
@ -924,10 +924,11 @@ public final class DownloadManager {
Log.e(TAG, "Failed to load downloads."); Log.e(TAG, "Failed to load downloads.");
} }
for (int i = 0; i < downloads.size(); i++) { for (int i = 0; i < downloads.size(); i++) {
downloads.set(i, copyDownloadWithState(downloads.get(i), STATE_REMOVING)); downloads.set(i, copyDownloadWithState(downloads.get(i), STATE_REMOVING, STOP_REASON_NONE));
} }
for (int i = 0; i < terminalDownloads.size(); i++) { for (int i = 0; i < terminalDownloads.size(); i++) {
downloads.add(copyDownloadWithState(terminalDownloads.get(i), STATE_REMOVING)); downloads.add(
copyDownloadWithState(terminalDownloads.get(i), STATE_REMOVING, STOP_REASON_NONE));
} }
Collections.sort(downloads, InternalHandler::compareStartTimes); Collections.sort(downloads, InternalHandler::compareStartTimes);
try { try {
@ -1019,7 +1020,7 @@ public final class DownloadManager {
} }
// We can start a download task. // We can start a download task.
download = putDownloadWithState(download, STATE_DOWNLOADING); download = putDownloadWithState(download, STATE_DOWNLOADING, STOP_REASON_NONE);
Downloader downloader = downloaderFactory.createDownloader(download.request); Downloader downloader = downloaderFactory.createDownloader(download.request);
activeTask = activeTask =
new Task( new Task(
@ -1041,7 +1042,7 @@ public final class DownloadManager {
Task activeTask, Download download, int accumulatingDownloadTaskCount) { Task activeTask, Download download, int accumulatingDownloadTaskCount) {
Assertions.checkState(!activeTask.isRemove); Assertions.checkState(!activeTask.isRemove);
if (!canDownloadsRun() || accumulatingDownloadTaskCount >= maxParallelDownloads) { if (!canDownloadsRun() || accumulatingDownloadTaskCount >= maxParallelDownloads) {
putDownloadWithState(download, STATE_QUEUED); putDownloadWithState(download, STATE_QUEUED, STOP_REASON_NONE);
activeTask.cancel(/* released= */ false); activeTask.cancel(/* released= */ false);
} }
} }
@ -1161,8 +1162,9 @@ public final class DownloadManager {
private void onRemoveTaskStopped(Download download) { private void onRemoveTaskStopped(Download download) {
if (download.state == STATE_RESTARTING) { if (download.state == STATE_RESTARTING) {
putDownloadWithState( @Download.State
download, download.stopReason == STOP_REASON_NONE ? STATE_QUEUED : STATE_STOPPED); int state = download.stopReason == STOP_REASON_NONE ? STATE_QUEUED : STATE_STOPPED;
putDownloadWithState(download, state, download.stopReason);
syncTasks(); syncTasks();
} else { } else {
int removeIndex = getDownloadIndex(download.request.id); int removeIndex = getDownloadIndex(download.request.id);
@ -1204,12 +1206,11 @@ public final class DownloadManager {
return !downloadsPaused && notMetRequirements == 0; return !downloadsPaused && notMetRequirements == 0;
} }
private Download putDownloadWithState(Download download, @Download.State int state) { private Download putDownloadWithState(
// Downloads in terminal states shouldn't be in the downloads list. This method cannot be used Download download, @Download.State int state, int stopReason) {
// to set STATE_STOPPED either, because it doesn't have a stopReason argument. // Downloads in terminal states shouldn't be in the downloads list.
Assertions.checkState( Assertions.checkState(state != STATE_COMPLETED && state != STATE_FAILED);
state != STATE_COMPLETED && state != STATE_FAILED && state != STATE_STOPPED); return putDownload(copyDownloadWithState(download, state, stopReason));
return putDownload(copyDownloadWithState(download, state));
} }
private Download putDownload(Download download) { private Download putDownload(Download download) {
@ -1267,14 +1268,15 @@ public final class DownloadManager {
return C.INDEX_UNSET; return C.INDEX_UNSET;
} }
private static Download copyDownloadWithState(Download download, @Download.State int state) { private static Download copyDownloadWithState(
Download download, @Download.State int state, int stopReason) {
return new Download( return new Download(
download.request, download.request,
state, state,
download.startTimeMs, download.startTimeMs,
/* updateTimeMs= */ System.currentTimeMillis(), /* updateTimeMs= */ System.currentTimeMillis(),
download.contentLength, download.contentLength,
/* stopReason= */ 0, stopReason,
FAILURE_REASON_NONE, FAILURE_REASON_NONE,
download.progress); download.progress);
} }

View file

@ -582,6 +582,64 @@ public class DownloadManagerTest {
assertThat(download2.state).isEqualTo(Download.STATE_REMOVING); assertThat(download2.state).isEqualTo(Download.STATE_REMOVING);
} }
@Test
public void addDownload_whilstRemovingWithStopReason_addsStartedDownload() throws Throwable {
runOnMainThread(
() -> downloadManager.addDownload(createDownloadRequest(ID1), /* stopReason= */ 1234));
postRemoveRequest(ID1);
FakeDownloader downloadRemover = getDownloaderAt(0);
downloadRemover.assertRemoveStarted();
// Re-add the download without a stop reason.
postDownloadRequest(ID1);
downloadRemover.finish();
FakeDownloader downloader = getDownloaderAt(1);
downloader.finish();
assertDownloadIndexSize(1);
// We expect one downloader for the removal, and one for when the download was re-added.
assertDownloaderCount(2);
// The download has completed, and so is no longer current.
assertCurrentDownloadCount(0);
Download download = postGetDownloadIndex().getDownload(ID1);
assertThat(download.state).isEqualTo(Download.STATE_COMPLETED);
assertThat(download.stopReason).isEqualTo(0);
}
/** Test for https://github.com/google/ExoPlayer/issues/8419 */
@Test
public void addDownloadWithStopReason_whilstRemoving_addsStoppedDownload() throws Throwable {
postDownloadRequest(ID1);
getDownloaderAt(0).finish();
postRemoveRequest(ID1);
FakeDownloader downloadRemover = getDownloaderAt(1);
downloadRemover.assertRemoveStarted();
// Re-add the download with a stop reason.
runOnMainThread(
() -> downloadManager.addDownload(createDownloadRequest(ID1), /* stopReason= */ 1234));
downloadRemover.finish();
assertDownloadIndexSize(1);
// We expect one downloader for the initial download, and one for the removal. A downloader
// should not be created when the download is re-added, since a stop reason is specified.
assertDownloaderCount(2);
// The download isn't completed, and is therefore still current.
assertCurrentDownloadCount(1);
List<Download> downloads = postGetCurrentDownloads();
Download download = downloads.get(0);
assertThat(download.request.id).isEqualTo(ID1);
assertThat(download.state).isEqualTo(Download.STATE_STOPPED);
assertThat(download.stopReason).isEqualTo(1234);
}
@Test @Test
public void mergeRequest_removing_becomesRestarting() { public void mergeRequest_removing_becomesRestarting() {
DownloadRequest downloadRequest = createDownloadRequest(ID1); DownloadRequest downloadRequest = createDownloadRequest(ID1);