Infer error code in network-based DataSourceException.

In some DataSources, it is not easy to assign an error code at the throw site.
For example, CronetDataSource.readInternal() throws SocketTimeoutException
on L1033, and is caught at L754 as IOException and is thrown.

We need the logic to assign error code for the actual type of the error cause.
While we can certainly do in individual DataSources, IMO there's value in
making this logic generic at a higher level (like what is in this CL).

The catch and translation logic is borrowed from EPII:L646.

PiperOrigin-RevId: 385789629
This commit is contained in:
claincly 2021-07-20 16:15:52 +01:00 committed by Ian Baker
parent 14e582b7e5
commit e4c9078a0c
4 changed files with 105 additions and 31 deletions

View file

@ -18,6 +18,8 @@ package com.google.android.exoplayer2.upstream;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.PlaybackException;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
/** Used to specify reason of a DataSource error. */
public class DataSourceException extends IOException {
@ -78,7 +80,7 @@ public class DataSourceException extends IOException {
public DataSourceException(
String message, Throwable cause, @PlaybackException.ErrorCode int reason) {
super(message, cause);
this.reason = reason;
this.reason = inferErrorCode(reason, cause);
}
/**
@ -90,7 +92,7 @@ public class DataSourceException extends IOException {
*/
public DataSourceException(Throwable cause, @PlaybackException.ErrorCode int reason) {
super(cause);
this.reason = reason;
this.reason = inferErrorCode(reason, cause);
}
/**
@ -104,4 +106,24 @@ public class DataSourceException extends IOException {
super(message);
this.reason = reason;
}
@PlaybackException.ErrorCode
private static int inferErrorCode(
@PlaybackException.ErrorCode int reason, @Nullable Throwable cause) {
if (reason != PlaybackException.ERROR_CODE_IO_UNSPECIFIED) {
return reason;
}
while (cause != null) {
if (cause instanceof UnknownHostException) {
return PlaybackException.ERROR_CODE_IO_DNS_FAILED;
} else if (cause instanceof SocketTimeoutException) {
return PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT;
} else if (cause instanceof DataSourceException) {
return ((DataSourceException) cause).reason;
}
cause = cause.getCause();
}
return PlaybackException.ERROR_CODE_IO_UNSPECIFIED;
}
}

View file

@ -231,7 +231,7 @@ public interface HttpDataSource extends DataSource {
*/
public HttpDataSourceException(
DataSpec dataSpec, @PlaybackException.ErrorCode int errorCode, @Type int type) {
super(errorCode);
super(assignErrorCode(errorCode, type));
this.dataSpec = dataSpec;
this.type = type;
}
@ -260,7 +260,7 @@ public interface HttpDataSource extends DataSource {
DataSpec dataSpec,
@PlaybackException.ErrorCode int errorCode,
@Type int type) {
super(message, errorCode);
super(message, assignErrorCode(errorCode, type));
this.dataSpec = dataSpec;
this.type = type;
}
@ -289,7 +289,7 @@ public interface HttpDataSource extends DataSource {
DataSpec dataSpec,
@PlaybackException.ErrorCode int errorCode,
@Type int type) {
super(cause, errorCode);
super(cause, assignErrorCode(errorCode, type));
this.dataSpec = dataSpec;
this.type = type;
}
@ -321,10 +321,17 @@ public interface HttpDataSource extends DataSource {
DataSpec dataSpec,
@PlaybackException.ErrorCode int errorCode,
@Type int type) {
super(message, cause, errorCode);
super(message, cause, assignErrorCode(errorCode, type));
this.dataSpec = dataSpec;
this.type = type;
}
@PlaybackException.ErrorCode
private static int assignErrorCode(@PlaybackException.ErrorCode int errorCode, @Type int type) {
return errorCode == PlaybackException.ERROR_CODE_IO_UNSPECIFIED && type == TYPE_OPEN
? PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED
: errorCode;
}
}
/**

View file

@ -17,9 +17,12 @@ package com.google.android.exoplayer2.upstream;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.PlaybackException;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -55,4 +58,68 @@ public class DataSourceExceptionTest {
IOException e = new IOException(new IOException(cause));
assertThat(DataSourceException.isCausedByPositionOutOfRange(e)).isFalse();
}
@Test
public void constructor_withNestedCausesAndUnspecifiedErrorCodes_assignsCorrectErrorCodes() {
DataSourceException exception =
new DataSourceException(
new UnknownHostException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
assertThat(exception.reason).isEqualTo(PlaybackException.ERROR_CODE_IO_DNS_FAILED);
exception =
new DataSourceException(
new SocketTimeoutException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
assertThat(exception.reason)
.isEqualTo(PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT);
exception =
new DataSourceException(
new IOException(new SocketTimeoutException()),
PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
assertThat(exception.reason)
.isEqualTo(PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT);
exception =
new DataSourceException(
new DataSourceException(
new SocketTimeoutException(), PlaybackException.ERROR_CODE_IO_UNSPECIFIED),
PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
assertThat(exception.reason)
.isEqualTo(PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT);
exception =
new DataSourceException(
new DataSourceException(
new DataSourceException(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE),
PlaybackException.ERROR_CODE_IO_UNSPECIFIED),
PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
assertThat(exception.reason)
.isEqualTo(PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE);
exception =
new DataSourceException(
new HttpDataSource.CleartextNotPermittedException(
new IOException(), new DataSpec(Uri.parse("test"))),
PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
assertThat(exception.reason).isEqualTo(PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED);
exception =
new DataSourceException(
new HttpDataSource.HttpDataSourceException(
new IOException(),
new DataSpec(Uri.parse("test")),
PlaybackException.ERROR_CODE_IO_UNSPECIFIED,
HttpDataSource.HttpDataSourceException.TYPE_OPEN),
PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
assertThat(exception.reason)
.isEqualTo(PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED);
exception =
new DataSourceException(
new DataSourceException(
new DataSourceException(PlaybackException.ERROR_CODE_IO_UNSPECIFIED),
PlaybackException.ERROR_CODE_IO_UNSPECIFIED),
PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
assertThat(exception.reason).isEqualTo(PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
}
}

View file

@ -48,9 +48,8 @@ import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSourceException;
import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.UdpDataSource;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
@ -60,8 +59,6 @@ import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -598,27 +595,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
errorCode = PlaybackException.ERROR_CODE_UNSPECIFIED;
}
handleIoException(e, errorCode);
} catch (HttpDataSource.CleartextNotPermittedException e) {
handleIoException(e, PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED);
} catch (HttpDataSource.InvalidResponseCodeException e) {
handleIoException(e, PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS);
} catch (HttpDataSource.HttpDataSourceException | UdpDataSource.UdpDataSourceException e) {
@ErrorCode int errorCode;
@Nullable Throwable cause = e.getCause();
if (cause instanceof UnknownHostException) {
errorCode = PlaybackException.ERROR_CODE_IO_DNS_FAILED;
} else if (cause instanceof SocketTimeoutException) {
errorCode = PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT;
} else if (e instanceof HttpDataSource.HttpDataSourceException) {
int type = ((HttpDataSource.HttpDataSourceException) e).type;
errorCode =
type == HttpDataSource.HttpDataSourceException.TYPE_OPEN
? PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED
: PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_CLOSED;
} else {
errorCode = PlaybackException.ERROR_CODE_IO_UNSPECIFIED;
}
handleIoException(e, errorCode);
} catch (DataSourceException e) {
handleIoException(e, e.reason);
} catch (BehindLiveWindowException e) {
handleIoException(e, PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW);
} catch (IOException e) {