src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java
changeset 49944 4690a2871b44
parent 49765 ee6f7a61f3a5
child 50681 4254bed3c09d
child 56507 2294c51eae30
--- a/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java	Wed May 02 10:47:16 2018 +0200
+++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java	Wed May 02 02:36:17 2018 -0700
@@ -121,33 +121,66 @@
             Utils.getHpackLogger(this::dbgString, Utils.DEBUG_HPACK);
     static final ByteBuffer EMPTY_TRIGGER = ByteBuffer.allocate(0);
 
-    private boolean singleStream; // used only for stream 1, then closed
+    static private final int MAX_CLIENT_STREAM_ID = Integer.MAX_VALUE; // 2147483647
+    static private final int MAX_SERVER_STREAM_ID = Integer.MAX_VALUE - 1; // 2147483646
+
+    /**
+     * Flag set when no more streams to be opened on this connection.
+     * Two cases where it is used.
+     *
+     * 1. Two connections to the same server were opened concurrently, in which
+     *    case one of them will be put in the cache, and the second will expire
+     *    when all its opened streams (which usually should be a single client
+     *    stream + possibly some additional push-promise server streams) complete.
+     * 2. A cached connection reaches its maximum number of streams (~ 2^31-1)
+     *    either server / or client allocated, in which case it will be taken
+     *    out of the cache - allowing a new connection to replace it. It will
+     *    expire when all its still open streams (which could be many) eventually
+     *    complete.
+     */
+    private boolean finalStream;
 
     /*
-     *  ByteBuffer pooling strategy for HTTP/2 protocol:
+     * ByteBuffer pooling strategy for HTTP/2 protocol.
      *
      * In general there are 4 points where ByteBuffers are used:
-     *  - incoming/outgoing frames from/to ByteBuffers plus incoming/outgoing encrypted data
-     *    in case of SSL connection.
+     *  - incoming/outgoing frames from/to ByteBuffers plus incoming/outgoing
+     *    encrypted data in case of SSL connection.
      *
      * 1. Outgoing frames encoded to ByteBuffers.
-     *    Outgoing ByteBuffers are created with requited size and frequently small (except DataFrames, etc)
-     *    At this place no pools at all. All outgoing buffers should be collected by GC.
+     *
+     *  Outgoing ByteBuffers are created with required size and frequently
+     *  small (except DataFrames, etc). At this place no pools at all. All
+     *  outgoing buffers should eventually be collected by GC.
      *
      * 2. Incoming ByteBuffers (decoded to frames).
-     *    Here, total elimination of BB pool is not a good idea.
-     *    We don't know how many bytes we will receive through network.
-     * So here we allocate buffer of reasonable size. The following life of the BB:
-     * - If all frames decoded from the BB are other than DataFrame and HeaderFrame (and HeaderFrame subclasses)
-     *     BB is returned to pool,
-     * - If we decoded DataFrame from the BB. In that case DataFrame refers to subbuffer obtained by slice() method.
-     *     Such BB is never returned to pool and will be GCed.
-     * - If we decoded HeadersFrame from the BB. Then header decoding is performed inside processFrame method and
-     *     the buffer could be release to pool.
+     *
+     *  Here, total elimination of BB pool is not a good idea.
+     *  We don't know how many bytes we will receive through network.
      *
-     * 3. SLL encrypted buffers. Here another pool was introduced and all net buffers are to/from the pool,
-     *    because of we can't predict size encrypted packets.
+     *  A possible future improvement ( currently not implemented ):
+     *  Allocate buffers of reasonable size. The following life of the BB:
+     *   - If all frames decoded from the BB are other than DataFrame and
+     *     HeaderFrame (and HeaderFrame subclasses) BB is returned to pool,
+     *   - If a DataFrame is decoded from the BB. In that case DataFrame refers
+     *     to sub-buffer obtained by slice(). Such a BB is never returned to the
+     *     pool and will eventually be GC'ed.
+     *   - If a HeadersFrame is decoded from the BB. Then header decoding is
+     *     performed inside processFrame method and the buffer could be release
+     *     back to pool.
      *
+     * 3. SSL encrypted buffers ( received ).
+     *
+     *  The current implementation recycles encrypted buffers read from the
+     *  channel. The pool of buffers has a maximum size of 3, SocketTube.MAX_BUFFERS,
+     *  direct buffers which are shared by all connections on a given client.
+     *  The pool is used by all SSL connections - whether HTTP/1.1 or HTTP/2,
+     *  but only for SSL encrypted buffers that circulate between the SocketTube
+     *  Publisher and the SSLFlowDelegate Reader. Limiting the pool to this
+     *  particular segment allows the use of direct buffers, thus avoiding any
+     *  additional copy in the NIO socket channel implementation. See
+     *  HttpClientImpl.SSLDirectBufferSupplier, SocketTube.SSLDirectBufferSource,
+     *  and SSLTube.recycler.
      */
 
 
@@ -220,6 +253,12 @@
     private final Map<Integer,Stream<?>> streams = new ConcurrentHashMap<>();
     private int nextstreamid;
     private int nextPushStream = 2;
+    // actual stream ids are not allocated until the Headers frame is ready
+    // to be sent. The following two fields are updated as soon as a stream
+    // is created and assigned to a connection. They are checked before
+    // assigning a stream to a connection.
+    private int lastReservedClientStreamid = 1;
+    private int lastReservedServerStreamid = 0;
     private final Encoder hpackOut;
     private final Decoder hpackIn;
     final SettingsFrame clientSettings;
@@ -365,6 +404,29 @@
         return client2.client();
     }
 
+    // call these before assigning a request/stream to a connection
+    // if false returned then a new Http2Connection is required
+    // if true, the the stream may be assigned to this connection
+    synchronized boolean reserveStream(boolean clientInitiated) {
+        if (finalStream) {
+            return false;
+        }
+        if (clientInitiated && (lastReservedClientStreamid + 2) >= MAX_CLIENT_STREAM_ID) {
+            setFinalStream();
+            client2.deleteConnection(this);
+            return false;
+        } else if (!clientInitiated && (lastReservedServerStreamid + 2) >= MAX_SERVER_STREAM_ID) {
+            setFinalStream();
+            client2.deleteConnection(this);
+            return false;
+        }
+        if (clientInitiated)
+            lastReservedClientStreamid+=2;
+        else
+            lastReservedServerStreamid+=2;
+        return true;
+    }
+
     /**
      * Throws an IOException if h2 was not negotiated
      */
@@ -414,12 +476,16 @@
                 .thenCompose(checkAlpnCF);
     }
 
-    synchronized boolean singleStream() {
-        return singleStream;
+    synchronized boolean finalStream() {
+        return finalStream;
     }
 
-    synchronized void setSingleStream(boolean use) {
-        singleStream = use;
+    /**
+     * Mark this connection so no more streams created on it and it will close when
+     * all are complete.
+     */
+    synchronized void setFinalStream() {
+        finalStream = true;
     }
 
     static String keyFor(HttpConnection connection) {
@@ -693,6 +759,9 @@
         if (promisedStreamid != nextPushStream) {
             resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR);
             return;
+        } else if (!reserveStream(false)) {
+            resetStream(promisedStreamid, ResetFrame.REFUSED_STREAM);
+            return;
         } else {
             nextPushStream += 2;
         }
@@ -752,7 +821,7 @@
             // corresponding entry in the window controller.
             windowController.removeStream(streamid);
         }
-        if (singleStream() && streams.isEmpty()) {
+        if (finalStream() && streams.isEmpty()) {
             // should be only 1 stream, but there might be more if server push
             close();
         }