diff options
author | Koushik Dutta <koushd@gmail.com> | 2014-12-20 21:14:51 -0800 |
---|---|---|
committer | Koushik Dutta <koushd@gmail.com> | 2014-12-20 21:14:58 -0800 |
commit | 8aabbd1fb850e4a8cea54c45c8de9dc715c5c70a (patch) | |
tree | 17bdd75e217447dc871136cf10e5b57f2a275fdf /AndroidAsync/src/com | |
parent | 25aa60f130d5a2e96255eeb6d78b9858586ee476 (diff) | |
download | AndroidAsync-8aabbd1fb850e4a8cea54c45c8de9dc715c5c70a.tar.gz AndroidAsync-8aabbd1fb850e4a8cea54c45c8de9dc715c5c70a.tar.bz2 AndroidAsync-8aabbd1fb850e4a8cea54c45c8de9dc715c5c70a.zip |
Fix dangling connection issues in SPDY
Diffstat (limited to 'AndroidAsync/src/com')
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/future/MultiFuture.java | 50 | ||||
-rw-r--r-- | AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java | 77 |
2 files changed, 111 insertions, 16 deletions
diff --git a/AndroidAsync/src/com/koushikdutta/async/future/MultiFuture.java b/AndroidAsync/src/com/koushikdutta/async/future/MultiFuture.java new file mode 100644 index 0000000..4ec7b86 --- /dev/null +++ b/AndroidAsync/src/com/koushikdutta/async/future/MultiFuture.java @@ -0,0 +1,50 @@ +package com.koushikdutta.async.future; + +import java.util.ArrayList; + +/** + * Created by koush on 2/25/14. + */ +public class MultiFuture<T> extends SimpleFuture<T> { + ArrayList<FutureCallback<T>> callbacks; + + final FutureCallback<T> callback = new FutureCallback<T>() { + @Override + public void onCompleted(Exception e, T result) { + ArrayList<FutureCallback<T>> callbacks; + synchronized (MultiFuture.this) { + callbacks = MultiFuture.this.callbacks; + MultiFuture.this.callbacks = null; + } + + if (callbacks == null) + return; + for (FutureCallback<T> cb: callbacks) { + cb.onCompleted(e, result); + } + } + }; + + @Override + public MultiFuture<T> setCallback(FutureCallback<T> callback) { + synchronized (this) { + if (callbacks == null) + callbacks = new ArrayList<FutureCallback<T>>(); + callbacks.add(callback); + } + // so, there is a race condition where this internal callback could get + // executed twice, if two callbacks are added at the same time. + // however, it doesn't matter, as the actual retrieval and nulling + // of the callback list is done in another sync block. + // one of the invocations will actually invoke all the callbacks, + // while the other will not get a list back. + + // race: + // 1-ADD + // 2-ADD + // 1-INVOKE LIST + // 2-INVOKE NULL + super.setCallback(this.callback); + return this; + } +} diff --git a/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java b/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java index 6f6c629..5150069 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/spdy/SpdyMiddleware.java @@ -10,10 +10,10 @@ import com.koushikdutta.async.DataEmitter; import com.koushikdutta.async.callback.ConnectCallback; import com.koushikdutta.async.future.Cancellable; import com.koushikdutta.async.future.FutureCallback; +import com.koushikdutta.async.future.MultiFuture; import com.koushikdutta.async.future.SimpleCancellable; import com.koushikdutta.async.future.TransformFuture; import com.koushikdutta.async.http.AsyncHttpClient; -import com.koushikdutta.async.http.AsyncHttpClientMiddleware; import com.koushikdutta.async.http.AsyncHttpRequest; import com.koushikdutta.async.http.AsyncSSLEngineConfigurator; import com.koushikdutta.async.http.AsyncSSLSocketMiddleware; @@ -24,6 +24,7 @@ import com.koushikdutta.async.http.Protocol; import com.koushikdutta.async.http.body.AsyncHttpRequestBody; import com.koushikdutta.async.util.Charsets; +import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; @@ -119,7 +120,7 @@ public class SpdyMiddleware extends AsyncSSLSocketMiddleware { Field useSni; Method nativeGetNpnNegotiatedProtocol; Method nativeGetAlpnNegotiatedProtocol; - Hashtable<String, AsyncSpdyConnection> connections = new Hashtable<String, AsyncSpdyConnection>(); + Hashtable<String, MultiFuture<AsyncSpdyConnection>> connections = new Hashtable<String, MultiFuture<AsyncSpdyConnection>>(); boolean spdyEnabled; public boolean getSpdyEnabled() { @@ -159,6 +160,16 @@ public class SpdyMiddleware extends AsyncSSLSocketMiddleware { return pathAndQuery; } + private static class NoSpdyException extends Exception { + } + private static NoSpdyException NO_SPDY = new NoSpdyException(); + + private void noSpdy(String key) { + MultiFuture<AsyncSpdyConnection> conn = connections.remove(key); + if (conn != null) + conn.setComplete(NO_SPDY); + } + @Override protected AsyncSSLSocketWrapper.HandshakeCallback createHandshakeCallback(final GetSocketData data, final ConnectCallback callback) { return new AsyncSSLSocketWrapper.HandshakeCallback() { @@ -168,30 +179,46 @@ public class SpdyMiddleware extends AsyncSSLSocketMiddleware { callback.onConnectCompleted(e, socket); return; } + final String key = data.request.getUri().getHost(); try { long ptr = (Long)sslNativePointer.get(socket.getSSLEngine()); byte[] proto = (byte[])nativeGetAlpnNegotiatedProtocol.invoke(null, ptr); if (proto == null) { callback.onConnectCompleted(null, socket); + noSpdy(key); return; } String protoString = new String(proto); Protocol p = Protocol.get(protoString); if (p == null) { callback.onConnectCompleted(null, socket); + noSpdy(key); return; } - final AsyncSpdyConnection connection = new AsyncSpdyConnection(socket, Protocol.get(protoString)); - connection.sendConnectionPreface(); - - connections.put(data.request.getUri().getHost(), connection); - - data.request.logv("using new spdy connection for host: " + data.request.getUri().getHost()); - newSocket(data, connection, callback); + final AsyncSpdyConnection connection = new AsyncSpdyConnection(socket, Protocol.get(protoString)) { + boolean hasReceivedSettings; + @Override + public void settings(boolean clearPrevious, Settings settings) { + super.settings(clearPrevious, settings); + if (!hasReceivedSettings) { + try { + sendConnectionPreface(); + } catch (IOException e1) { + e1.printStackTrace(); + } + hasReceivedSettings = true; + + connections.get(key).setComplete(this); + data.request.logv("using new spdy connection for host: " + data.request.getUri().getHost()); + newSocket(data, this, callback); + } + } + }; } catch (Exception ex) { socket.close(); callback.onConnectCompleted(ex, null); + noSpdy(key); } } }; @@ -248,7 +275,7 @@ public class SpdyMiddleware extends AsyncSSLSocketMiddleware { } @Override - public Cancellable getSocket(GetSocketData data) { + public Cancellable getSocket(final GetSocketData data) { if (!spdyEnabled) return super.getSocket(data); @@ -266,17 +293,35 @@ public class SpdyMiddleware extends AsyncSSLSocketMiddleware { // can we use an existing connection to satisfy this, or do we need a new one? String host = uri.getHost(); - AsyncSpdyConnection conn = connections.get(host); - if (conn == null || !conn.socket.isOpen()) { - connections.remove(host); + MultiFuture<AsyncSpdyConnection> conn = connections.get(host); + if (conn == null) { + conn = new MultiFuture<AsyncSpdyConnection>(); + connections.put(host, conn); return super.getSocket(data); } + final SimpleCancellable ret = new SimpleCancellable(); data.request.logv("using existing spdy connection for host: " + data.request.getUri().getHost()); - newSocket(data, conn, data.connectCallback); + conn.setCallback(new FutureCallback<AsyncSpdyConnection>() { + @Override + public void onCompleted(Exception e, AsyncSpdyConnection conn) { + if (e instanceof NoSpdyException) { + data.request.logv("spdy not available"); + ret.setParent(SpdyMiddleware.super.getSocket(data)); + return; + } + if (e != null) { + data.request.loge("spdy not available", e); + ret.setComplete(); + data.connectCallback.onConnectCompleted(e, null); + return; + } + data.request.logv("spdy connection ready"); + ret.setComplete(); + newSocket(data, conn, data.connectCallback); + } + }); - SimpleCancellable ret = new SimpleCancellable(); - ret.setComplete(); return ret; } |