diff options
| author | Jesse Wilson <jwilson@squareup.com> | 2014-04-03 00:18:59 -0400 |
|---|---|---|
| committer | Neil Fuller <nfuller@google.com> | 2014-10-24 17:09:50 +0100 |
| commit | 87ed7244fb53ae2bac9f23c033bbd5f23ac269f8 (patch) | |
| tree | bab8314a797842e78895d5df69386c234c20815d | |
| parent | fe5706946b0c01b1370419c83b71b3b7f104c19e (diff) | |
| download | android_external_okhttp-87ed7244fb53ae2bac9f23c033bbd5f23ac269f8.tar.gz android_external_okhttp-87ed7244fb53ae2bac9f23c033bbd5f23ac269f8.tar.bz2 android_external_okhttp-87ed7244fb53ae2bac9f23c033bbd5f23ac269f8.zip | |
New disconnect strategy.
Support asynchronous disconnects by breaking the socket only, which
should cause the thread using that socket to trigger clean-up.
Upstream commit: https://github.com/square/okhttp/commit/9c302131491d05a4ca0209ef21770592c01f76fa
Bug: 18083851
Change-Id: I5f5eb648f4a5f2022c63acd7c903aac88e178d9a
8 files changed, 143 insertions, 3 deletions
diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/DisconnectTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/DisconnectTest.java new file mode 100644 index 0000000..db84214 --- /dev/null +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/DisconnectTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * 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.squareup.okhttp.internal.http; + +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.util.concurrent.TimeUnit; +import org.junit.Test; + +import static org.junit.Assert.fail; + +public final class DisconnectTest { + private final MockWebServer server = new MockWebServer(); + private final OkHttpClient client = new OkHttpClient(); + + @Test public void interruptWritingRequestBody() throws Exception { + int requestBodySize = 2 * 1024 * 1024; // 2 MiB + + server.enqueue(new MockResponse() + .throttleBody(64 * 1024, 125, TimeUnit.MILLISECONDS)); // 500 Kbps + server.play(); + + HttpURLConnection connection = client.open(server.getUrl("/")); + disconnectLater(connection, 500); + + connection.setDoOutput(true); + connection.setFixedLengthStreamingMode(requestBodySize); + OutputStream requestBody = connection.getOutputStream(); + byte[] buffer = new byte[1024]; + try { + for (int i = 0; i < requestBodySize; i += buffer.length) { + requestBody.write(buffer); + requestBody.flush(); + } + fail("Expected connection to be closed"); + } catch (IOException expected) { + } + + connection.disconnect(); + } + + @Test public void interruptReadingResponseBody() throws Exception { + int responseBodySize = 2 * 1024 * 1024; // 2 MiB + + server.enqueue(new MockResponse() + .setBody(new byte[responseBodySize]) + .throttleBody(64 * 1024, 125, TimeUnit.MILLISECONDS)); // 500 Kbps + server.play(); + + HttpURLConnection connection = client.open(server.getUrl("/")); + disconnectLater(connection, 500); + + InputStream responseBody = connection.getInputStream(); + byte[] buffer = new byte[1024]; + try { + while (responseBody.read(buffer) != -1) { + } + fail("Expected connection to be closed"); + } catch (IOException expected) { + } + + connection.disconnect(); + } + + private void disconnectLater(final HttpURLConnection connection, final int delayMillis) { + Thread interruptingCow = new Thread() { + @Override public void run() { + try { + sleep(delayMillis); + connection.disconnect(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }; + interruptingCow.start(); + } +} diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java index a9f902a..127807f 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java @@ -65,6 +65,7 @@ import java.util.Map; import java.util.Random; import java.util.Set; import java.util.UUID; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -1029,7 +1030,9 @@ public final class URLConnectionTest { } @Test public void disconnectedConnection() throws IOException { - server.enqueue(new MockResponse().setBody("ABCDEFGHIJKLMNOPQR")); + server.enqueue(new MockResponse() + .throttleBody(2, 100, TimeUnit.MILLISECONDS) + .setBody("ABCD")); server.play(); connection = client.open(server.getUrl("/")); @@ -1037,6 +1040,10 @@ public final class URLConnectionTest { assertEquals('A', (char) in.read()); connection.disconnect(); try { + // Reading 'B' may succeed if it's buffered. + in.read(); + + // But 'C' shouldn't be buffered (the response is throttled) and this should fail. in.read(); fail("Expected a connection closed exception"); } catch (IOException expected) { @@ -1317,11 +1324,13 @@ public final class URLConnectionTest { HttpURLConnection connection1 = client.open(server.getUrl("/")); InputStream in1 = connection1.getInputStream(); assertEquals("ABCDE", readAscii(in1, 5)); + in1.close(); connection1.disconnect(); HttpURLConnection connection2 = client.open(server.getUrl("/")); InputStream in2 = connection2.getInputStream(); assertEquals("LMNOP", readAscii(in2, 5)); + in2.close(); connection2.disconnect(); assertEquals(0, server.takeRequest().getSequenceNumber()); diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java index b12b12d..718d471 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java @@ -122,6 +122,10 @@ public final class HttpConnection { return state == STATE_CLOSED; } + public void closeIfOwnedBy(Object owner) throws IOException { + connection.closeIfOwnedBy(owner); + } + public void flush() throws IOException { sink.flush(); } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java index f00fbe7..d796a6c 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java @@ -411,6 +411,18 @@ public class HttpEngine { } /** + * Immediately closes the socket connection if it's currently held by this + * engine. Use this to interrupt an in-flight request from any thread. It's + * the caller's responsibility to close the request body and response body + * streams; otherwise resources may be leaked. + */ + public final void disconnect() throws IOException { + if (transport != null) { + transport.disconnect(this); + } + } + + /** * Release any resources held by this engine. If a connection is still held by * this engine, it is returned. */ diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java index a1b367f..2ffe039 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java @@ -150,4 +150,8 @@ public final class HttpTransport implements Transport { // reference escapes. return httpConnection.newUnknownLengthSource(cacheRequest); } + + @Override public void disconnect(HttpEngine engine) throws IOException { + httpConnection.closeIfOwnedBy(engine); + } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java index 899d914..32be0be 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java @@ -109,9 +109,18 @@ public class HttpURLConnectionImpl extends HttpURLConnection { @Override public final void disconnect() { // Calling disconnect() before a connection exists should have no effect. - if (httpEngine != null) { - httpEngine.close(); + if (httpEngine == null) return; + + try { + httpEngine.disconnect(); + } catch (IOException ignored) { } + + // This doesn't close the stream because doing so would require all stream + // access to be synchronized. It's expected that the thread using the + // connection will close its streams directly. If it doesn't, the worst + // case is that the GzipSource's Inflater won't be released until it's + // finalized. (This logs a warning on Android.) } /** diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java index e775d34..9db9643 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java @@ -219,6 +219,10 @@ public final class SpdyTransport implements Transport { @Override public void releaseConnectionOnIdle() { } + @Override public void disconnect(HttpEngine engine) throws IOException { + stream.close(ErrorCode.CANCEL); + } + @Override public boolean canReuseConnection() { return true; // TODO: spdyConnection.isClosed() ? } diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/Transport.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/Transport.java index 94c90d4..852a15b 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/Transport.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/Transport.java @@ -76,6 +76,8 @@ interface Transport { */ void releaseConnectionOnIdle() throws IOException; + void disconnect(HttpEngine engine) throws IOException; + /** * Returns true if the socket connection held by this transport can be reused * for a follow-up exchange. |
