aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Wilson <jwilson@squareup.com>2014-04-03 00:18:59 -0400
committerNeil Fuller <nfuller@google.com>2014-10-24 17:09:50 +0100
commit87ed7244fb53ae2bac9f23c033bbd5f23ac269f8 (patch)
treebab8314a797842e78895d5df69386c234c20815d
parentfe5706946b0c01b1370419c83b71b3b7f104c19e (diff)
downloadandroid_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
-rw-r--r--okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/DisconnectTest.java96
-rw-r--r--okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java11
-rw-r--r--okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java4
-rw-r--r--okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpEngine.java12
-rw-r--r--okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpTransport.java4
-rw-r--r--okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpURLConnectionImpl.java13
-rw-r--r--okhttp/src/main/java/com/squareup/okhttp/internal/http/SpdyTransport.java4
-rw-r--r--okhttp/src/main/java/com/squareup/okhttp/internal/http/Transport.java2
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.