Add LoadErrorHandlingPolicy to customize blacklisting and backoff logic

Issue:#2844
Issue:#3370
Issue:#2981

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=204149284
This commit is contained in:
aquilescanta 2018-07-11 10:42:28 -07:00 committed by Oliver Woodman
parent 095c6e4bf8
commit 32a91b5689
3 changed files with 231 additions and 11 deletions

View file

@ -0,0 +1,116 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.upstream;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
import com.google.android.exoplayer2.upstream.Loader.Loadable;
import java.io.IOException;
/**
* Defines how errors encountered by {@link Loader Loaders} are handled.
*
* <p>Loader clients may blacklist a resource when a load error occurs. Blacklisting works around
* load errors by loading an alternative resource. Clients do not try blacklisting when a resource
* does not have an alternative. When a resource does have valid alternatives, {@link
* #getBlacklistDurationMsFor(T, long, IOException, int)} defines whether the resource should be
* blacklisted. Blacklisting will succeed if any of the alternatives is not in the black list.
*
* <p>When blacklisting does not take place, {@link #getRetryDelayMsFor(T, long, IOException, int)}
* defines whether the load is retried. Loader clients define when to propagate retry attempt
* errors. Errors that are not retried are propagated.
*
* @param <T> The type of the object being loaded.
*/
public interface LoadErrorHandlingPolicy<T extends Loadable> {
/** Default implementation of {@link LoadErrorHandlingPolicy}. */
LoadErrorHandlingPolicy<Loadable> DEFAULT =
new LoadErrorHandlingPolicy<Loadable>() {
/**
* Blacklists resources whose load error was an {@link InvalidResponseCodeException} with
* response code HTTP 404 or 410. The duration of the blacklisting is {@link
* ChunkedTrackBlacklistUtil#DEFAULT_TRACK_BLACKLIST_MS}.
*/
@Override
public long getBlacklistDurationMsFor(
Loadable loadable, long loadDurationMs, IOException exception, int errorCount) {
if (exception instanceof InvalidResponseCodeException) {
int responseCode = ((InvalidResponseCodeException) exception).responseCode;
return responseCode == 404 // HTTP 404 Not Found.
|| responseCode == 410 // HTTP 410 Gone.
? ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS
: C.TIME_UNSET;
}
return C.TIME_UNSET;
}
/**
* Retries for any exception that is not a subclass of {@link ParserException}. The retry
* delay is calculated as {@code Math.min((errorCount - 1) * 1000, 5000)}.
*/
@Override
public long getRetryDelayMsFor(
Loadable loadable, long loadDurationMs, IOException exception, int errorCount) {
return exception instanceof ParserException
? C.TIME_UNSET
: Math.min((errorCount - 1) * 1000, 5000);
}
};
/** Returns {@link #DEFAULT}. */
static <U extends Loadable> LoadErrorHandlingPolicy<U> getDefault() {
@SuppressWarnings("unchecked") // Safe contravariant cast.
LoadErrorHandlingPolicy<U> policy = (LoadErrorHandlingPolicy<U>) DEFAULT;
return policy;
}
/**
* Returns the number of milliseconds for which a resource associated to a provided load error
* should be blacklisted, or {@link C#TIME_UNSET} if the resource should not be blacklisted.
*
* @param loadable The loadable whose load failed.
* @param loadDurationMs The duration in milliseconds of the load up to the point at which the
* error occurred, including any previous attempts.
* @param exception The load error.
* @param errorCount The number of errors this load has encountered, including this one.
* @return The blacklist duration in milliseconds, or {@link C#TIME_UNSET} if the resource should
* not be blacklisted.
*/
long getBlacklistDurationMsFor(
T loadable, long loadDurationMs, IOException exception, int errorCount);
/**
* Returns the number of milliseconds to wait before attempting the load again, or {@link
* C#TIME_UNSET} if the error is fatal and should not be retried.
*
* <p>{@link Loader} clients may ignore the retry delay returned by this method in order to wait
* for a specific event before retrying. However, the load is retried if and only if this method
* does not return {@link C#TIME_UNSET}.
*
* @param loadable The loadable whose load failed.
* @param loadDurationMs The duration in milliseconds of the load up to the point at which the
* error occurred, including any previous attempts.
* @param exception The load error.
* @param errorCount The number of errors this load has encountered, including this one.
* @return The number of milliseconds to wait before attempting the load again, or {@link
* C#TIME_UNSET} if the error is fatal and should not be retried.
*/
long getRetryDelayMsFor(T loadable, long loadDurationMs, IOException exception, int errorCount);
}

View file

@ -75,27 +75,29 @@ public final class Loader implements LoaderErrorThrower {
/**
* Called when a load has completed.
* <p>
* Note: There is guaranteed to be a memory barrier between {@link Loadable#load()} exiting and
* this callback being called.
*
* <p>Note: There is guaranteed to be a memory barrier between {@link Loadable#load()} exiting
* and this callback being called.
*
* @param loadable The loadable whose load has completed.
* @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the load ended.
* @param loadDurationMs The duration of the load.
* @param loadDurationMs The duration in milliseconds of the load since {@link #startLoading}
* was called.
*/
void onLoadCompleted(T loadable, long elapsedRealtimeMs, long loadDurationMs);
/**
* Called when a load has been canceled.
* <p>
* Note: If the {@link Loader} has not been released then there is guaranteed to be a memory
* barrier between {@link Loadable#load()} exiting and this callback being called. If the
* {@link Loader} has been released then this callback may be called before
* {@link Loadable#load()} exits.
*
* <p>Note: If the {@link Loader} has not been released then there is guaranteed to be a memory
* barrier between {@link Loadable#load()} exiting and this callback being called. If the {@link
* Loader} has been released then this callback may be called before {@link Loadable#load()}
* exits.
*
* @param loadable The loadable whose load has been canceled.
* @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the load was canceled.
* @param loadDurationMs The duration of the load up to the point at which it was canceled.
* @param loadDurationMs The duration in milliseconds of the load since {@link #startLoading}
* was called up to the point at which it was canceled.
* @param released True if the load was canceled because the {@link Loader} was released. False
* otherwise.
*/
@ -109,7 +111,8 @@ public final class Loader implements LoaderErrorThrower {
*
* @param loadable The loadable whose load has encountered an error.
* @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the error occurred.
* @param loadDurationMs The duration of the load up to the point at which the error occurred.
* @param loadDurationMs The duration in milliseconds of the load since {@link #startLoading}
* was called up to the point at which the error occurred.
* @param error The load error.
* @param errorCount The number of errors this load has encountered, including this one.
* @return The desired error handling action. One of {@link Loader#RETRY}, {@link

View file

@ -0,0 +1,101 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.upstream;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil;
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException;
import com.google.android.exoplayer2.upstream.Loader.Loadable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Unit tests for {@link LoadErrorHandlingPolicy#DEFAULT}. */
@RunWith(RobolectricTestRunner.class)
public final class DefaultLoadErrorHandlingPolicyTest {
private static final Loadable DUMMY_LOADABLE =
new Loadable() {
@Override
public void cancelLoad() {
// Do nothing.
}
@Override
public void load() throws IOException, InterruptedException {
// Do nothing.
}
};
@Test
public void getBlacklistDurationMsFor_blacklist404() throws Exception {
InvalidResponseCodeException exception =
new InvalidResponseCodeException(404, Collections.emptyMap(), new DataSpec(Uri.EMPTY));
assertThat(getDefaultPolicyBlacklistOutputFor(exception))
.isEqualTo(ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
}
@Test
public void getBlacklistDurationMsFor_blacklist410() throws Exception {
InvalidResponseCodeException exception =
new InvalidResponseCodeException(410, Collections.emptyMap(), new DataSpec(Uri.EMPTY));
assertThat(getDefaultPolicyBlacklistOutputFor(exception))
.isEqualTo(ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
}
@Test
public void getBlacklistDurationMsFor_dontBlacklistUnexpectedHttpCodes() throws Exception {
InvalidResponseCodeException exception =
new InvalidResponseCodeException(500, Collections.emptyMap(), new DataSpec(Uri.EMPTY));
assertThat(getDefaultPolicyBlacklistOutputFor(exception)).isEqualTo(C.TIME_UNSET);
}
@Test
public void getBlacklistDurationMsFor_dontBlacklistUnexpectedExceptions() throws Exception {
FileNotFoundException exception = new FileNotFoundException();
assertThat(getDefaultPolicyBlacklistOutputFor(exception)).isEqualTo(C.TIME_UNSET);
}
@Test
public void getRetryDelayMsFor_dontRetryParserException() throws Exception {
assertThat(getDefaultPolicyRetryDelayOutputFor(new ParserException(), 1))
.isEqualTo(C.TIME_UNSET);
}
@Test
public void getRetryDelayMsFor_successiveRetryDelays() throws Exception {
assertThat(getDefaultPolicyRetryDelayOutputFor(new FileNotFoundException(), 3)).isEqualTo(2000);
assertThat(getDefaultPolicyRetryDelayOutputFor(new FileNotFoundException(), 5)).isEqualTo(4000);
assertThat(getDefaultPolicyRetryDelayOutputFor(new FileNotFoundException(), 9)).isEqualTo(5000);
}
private static long getDefaultPolicyBlacklistOutputFor(IOException exception) {
return LoadErrorHandlingPolicy.DEFAULT.getBlacklistDurationMsFor(
DUMMY_LOADABLE, /* loadDurationMs= */ 1000, exception, /* errorCount= */ 1);
}
private static long getDefaultPolicyRetryDelayOutputFor(IOException exception, int errorCount) {
return LoadErrorHandlingPolicy.DEFAULT.getRetryDelayMsFor(
DUMMY_LOADABLE, /* loadDurationMs= */ 1000, exception, errorCount);
}
}