Workaround for OkHttp Interrupt issues.

Relates to https://github.com/square/okhttp/issues/3146. This was from https://github.com/androidx/media/pull/71.

There is a draft PR https://github.com/square/okhttp/pull/7185/files which documents OkHttp's ideal handling of cancellation including interrupts.

But a few key points

1) This is a target state, and OkHttp does not currently handle interrupts correctly.  In the past this has been identified, and the advice is to avoid interrupts on Http threads, see discussion on https://github.com/square/okhttp/issues/1902. Also an attempt at a fix here https://github.com/square/okhttp/pull/7023 which wasn't in a form to land.

2) Even with this fixed, it is likely to never be optimal, because of OkHttp sharing a socket connection for multiple inflight requests.

From https://github.com/square/okhttp/pull/7185

```
Thread.interrupt() is Clumsy
----------------------------

`Thread.interrupt()` is Java's built-in mechanism to cancel an in-flight `Thread`, regardless of
what work it's currently performing.

We recommend against using `Thread.interrupt()` with OkHttp because it may disrupt shared resources
including HTTP/2 connections and cache files. In particular, calling `Thread.interrupt()` may cause
unrelated threads' call to fail with an `IOException`.
```

This PR leaves the Loader/DataSource thread parked on a countdown latch, while this may seem wasteful and an additional context switch. However in practice the response isn't returned until the Http2Connection and Http2Reader have a response from the server and these means effectively parking in a `wait()` statement here 9e039e9412/okhttp/src/jvmMain/kotlin/okhttp3/internal/http2/Http2Stream.kt (L140)

PiperOrigin-RevId: 446652468
This commit is contained in:
yschimke 2022-05-05 09:15:06 +01:00 committed by Ian Baker
parent 4b8ff72d30
commit 80928e730c
2 changed files with 38 additions and 1 deletions

View File

@ -89,6 +89,7 @@
* Data sources: * Data sources:
* Rename `DummyDataSource` to `PlaceHolderDataSource`. * Rename `DummyDataSource` to `PlaceHolderDataSource`.
* Workaround OkHttp interrupt handling.
* Remove deprecated symbols: * Remove deprecated symbols:
* Remove `Player.Listener.onTracksChanged`. Use * Remove `Player.Listener.onTracksChanged`. Use
`Player.Listener.onTracksInfoChanged` instead. `Player.Listener.onTracksInfoChanged` instead.

View File

@ -32,10 +32,14 @@ import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSourceException; import androidx.media3.datasource.DataSourceException;
import androidx.media3.datasource.DataSpec; import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.HttpDataSource; import androidx.media3.datasource.HttpDataSource;
import androidx.media3.datasource.HttpDataSource.HttpDataSourceException;
import androidx.media3.datasource.HttpDataSource.InvalidContentTypeException;
import androidx.media3.datasource.HttpDataSource.InvalidResponseCodeException;
import androidx.media3.datasource.HttpUtil; import androidx.media3.datasource.HttpUtil;
import androidx.media3.datasource.TransferListener; import androidx.media3.datasource.TransferListener;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.net.HttpHeaders; import com.google.common.net.HttpHeaders;
import com.google.common.util.concurrent.SettableFuture;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
@ -43,8 +47,10 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException;
import okhttp3.CacheControl; import okhttp3.CacheControl;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -299,8 +305,9 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
Request request = makeRequest(dataSpec); Request request = makeRequest(dataSpec);
Response response; Response response;
ResponseBody responseBody; ResponseBody responseBody;
Call call = callFactory.newCall(request);
try { try {
this.response = callFactory.newCall(request).execute(); this.response = executeCall(call);
response = this.response; response = this.response;
responseBody = Assertions.checkNotNull(response.body()); responseBody = Assertions.checkNotNull(response.body());
responseByteStream = responseBody.byteStream(); responseByteStream = responseBody.byteStream();
@ -448,6 +455,35 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
return builder.build(); return builder.build();
} }
/**
* This method is an interrupt safe replacement of OkHttp Call.execute() which can get in bad
* states if interrupted while writing to the shared connection socket.
*/
private Response executeCall(Call call) throws IOException {
SettableFuture<Response> future = SettableFuture.create();
call.enqueue(
new Callback() {
@Override
public void onFailure(Call call, IOException e) {
future.setException(e);
}
@Override
public void onResponse(Call call, Response response) {
future.set(response);
}
});
try {
return future.get();
} catch (InterruptedException e) {
call.cancel();
throw new InterruptedIOException();
} catch (ExecutionException ee) {
throw new IOException(ee);
}
}
/** /**
* Attempts to skip the specified number of bytes in full. * Attempts to skip the specified number of bytes in full.
* *