You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2021/11/10 21:18:41 UTC

[httpcomponents-core] branch 5.1.x updated (d78555c -> 06751c1)

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a change to branch 5.1.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git.


    from d78555c  HTTPCORE-694: Fixed decrypted data check by non-blocking SSL i/o sessions  (#317)
     new 7ae878d  HTTPCORE-696: H2 protocol handler to ensure minimal processing of incoming frames during a graceful shutdown.
     new 06751c1  H2 stream resource release optimization

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../impl/nio/AbstractH2StreamMultiplexer.java      | 71 +++++++++++++++-------
 ...StreamHandler.java => NoopH2StreamHandler.java} | 47 ++++++++++----
 2 files changed, 85 insertions(+), 33 deletions(-)
 copy httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/{H2StreamHandler.java => NoopH2StreamHandler.java} (57%)

[httpcomponents-core] 01/02: HTTPCORE-696: H2 protocol handler to ensure minimal processing of incoming frames during a graceful shutdown.

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch 5.1.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 7ae878d018f630cd635662c8954924200614b830
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Wed Nov 10 11:22:54 2021 +0100

    HTTPCORE-696: H2 protocol handler to ensure minimal processing of incoming frames during a graceful shutdown.
---
 .../impl/nio/AbstractH2StreamMultiplexer.java      | 65 +++++++++++------
 .../core5/http2/impl/nio/NoopH2StreamHandler.java  | 84 ++++++++++++++++++++++
 2 files changed, 128 insertions(+), 21 deletions(-)

diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
index 6229b07..885238f 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
@@ -26,7 +26,6 @@
  */
 package org.apache.hc.core5.http2.impl.nio;
 
-import javax.net.ssl.SSLSession;
 import java.io.IOException;
 import java.net.SocketAddress;
 import java.nio.ByteBuffer;
@@ -43,6 +42,8 @@ import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import javax.net.ssl.SSLSession;
+
 import org.apache.hc.core5.concurrent.Cancellable;
 import org.apache.hc.core5.concurrent.CancellableDependency;
 import org.apache.hc.core5.http.ConnectionClosedException;
@@ -133,6 +134,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
 
     private int processedRemoteStreamId;
     private EndpointDetails endpointDetails;
+    private boolean goAwayReceived;
 
     AbstractH2StreamMultiplexer(
             final ProtocolIOSession ioSession,
@@ -503,19 +505,30 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
             processPendingCommands();
         }
         if (connState.compareTo(ConnectionHandshake.GRACEFUL_SHUTDOWN) == 0) {
+            int liveStreams = 0;
             for (final Iterator<Map.Entry<Integer, H2Stream>> it = streamMap.entrySet().iterator(); it.hasNext(); ) {
                 final Map.Entry<Integer, H2Stream> entry = it.next();
                 final H2Stream stream = entry.getValue();
                 if (stream.isLocalClosed() && stream.isRemoteClosed()) {
                     stream.releaseResources();
                     it.remove();
+                } else {
+                    if (idGenerator.isSameSide(stream.getId()) || stream.getId() <= processedRemoteStreamId) {
+                        liveStreams++;
+                    }
                 }
             }
-            if (streamMap.isEmpty()) {
+            if (liveStreams == 0) {
                 connState = ConnectionHandshake.SHUTDOWN;
             }
         }
         if (connState.compareTo(ConnectionHandshake.SHUTDOWN) >= 0) {
+            if (!streamMap.isEmpty()) {
+                for (final H2Stream stream : streamMap.values()) {
+                    stream.releaseResources();
+                }
+                streamMap.clear();
+            }
             ioSession.getLock().lock();
             try {
                 if (outputBuffer.isEmpty() && outputQueue.isEmpty()) {
@@ -722,12 +735,6 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
         if (continuation != null && frameType != FrameType.CONTINUATION) {
             throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "CONTINUATION frame expected");
         }
-        if (connState.compareTo(ConnectionHandshake.GRACEFUL_SHUTDOWN) >= 0) {
-            if (streamId > processedRemoteStreamId && !idGenerator.isSameSide(streamId)) {
-                // ignore the frame
-                return;
-            }
-        }
         switch (frameType) {
             case DATA: {
                 final H2Stream stream = getValidStream(streamId);
@@ -753,12 +760,25 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
                 if (stream == null) {
                     acceptHeaderFrame();
 
+                    if (idGenerator.isSameSide(streamId)) {
+                        throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Illegal stream id: " + streamId);
+                    }
+                    if (goAwayReceived ) {
+                        throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "GOAWAY received");
+                    }
+
                     updateLastStreamId(streamId);
 
                     final H2StreamChannelImpl channel = new H2StreamChannelImpl(
                             streamId, false, initInputWinSize, initOutputWinSize);
-                    final H2StreamHandler streamHandler = createRemotelyInitiatedStream(
-                            channel, httpProcessor, connMetrics, null);
+                    final H2StreamHandler streamHandler;
+                    if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0) {
+                        streamHandler = createRemotelyInitiatedStream(channel, httpProcessor, connMetrics, null);
+                    } else {
+                        streamHandler = NoopH2StreamHandler.INSTANCE;
+                        channel.setLocalEndStream();
+                    }
+
                     stream = new H2Stream(channel, streamHandler, true);
                     if (stream.isOutputReady()) {
                         stream.produceOutput();
@@ -913,6 +933,10 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
             case PUSH_PROMISE: {
                 acceptPushFrame();
 
+                if (goAwayReceived ) {
+                    throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "GOAWAY received");
+                }
+
                 if (!localConfig.isPushEnabled()) {
                     throw new H2ConnectionException(H2Error.PROTOCOL_ERROR, "Push is disabled");
                 }
@@ -939,8 +963,15 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
 
                 final H2StreamChannelImpl channel = new H2StreamChannelImpl(
                         promisedStreamId, false, initInputWinSize, initOutputWinSize);
-                final H2StreamHandler streamHandler = createRemotelyInitiatedStream(
-                        channel, httpProcessor, connMetrics, stream.getPushHandlerFactory());
+                final H2StreamHandler streamHandler;
+                if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0) {
+                    streamHandler = createRemotelyInitiatedStream(channel, httpProcessor, connMetrics,
+                            stream.getPushHandlerFactory());
+                } else {
+                    streamHandler = NoopH2StreamHandler.INSTANCE;
+                    channel.setLocalEndStream();
+                }
+
                 final H2Stream promisedStream = new H2Stream(channel, streamHandler, true);
                 streamMap.put(promisedStreamId, promisedStream);
 
@@ -963,6 +994,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
                 }
                 final int processedLocalStreamId = payload.getInt();
                 final int errorCode = payload.getInt();
+                goAwayReceived = true;
                 if (errorCode == H2Error.NO_ERROR.getCode()) {
                     if (connState.compareTo(ConnectionHandshake.ACTIVE) <= 0) {
                         for (final Iterator<Map.Entry<Integer, H2Stream>> it = streamMap.entrySet().iterator(); it.hasNext(); ) {
@@ -1039,9 +1071,6 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
             if (streamListener != null) {
                 streamListener.onHeaderInput(this, promisedStreamId, headers);
             }
-            if (connState == ConnectionHandshake.GRACEFUL_SHUTDOWN) {
-                throw new H2StreamResetException(H2Error.REFUSED_STREAM, "Stream refused");
-            }
             promisedStream.consumePromise(headers);
         } else {
             continuation.copyPayload(payload);
@@ -1077,9 +1106,6 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
             if (stream.isLocalReset()) {
                 return;
             }
-            if (connState == ConnectionHandshake.GRACEFUL_SHUTDOWN) {
-                throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream refused");
-            }
             if (frame.isFlagSet(FrameFlag.END_STREAM)) {
                 stream.setRemoteEndStream();
             }
@@ -1101,9 +1127,6 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
             if (streamListener != null) {
                 streamListener.onHeaderInput(this, streamId, headers);
             }
-            if (connState == ConnectionHandshake.GRACEFUL_SHUTDOWN) {
-                throw new H2StreamResetException(H2Error.PROTOCOL_ERROR, "Stream refused");
-            }
             if (stream.isRemoteClosed()) {
                 throw new H2StreamResetException(H2Error.STREAM_CLOSED, "Stream already closed");
             }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/NoopH2StreamHandler.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/NoopH2StreamHandler.java
new file mode 100644
index 0000000..5bbc3d4
--- /dev/null
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/NoopH2StreamHandler.java
@@ -0,0 +1,84 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.hc.core5.http2.impl.nio;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.apache.hc.core5.http.Header;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.nio.AsyncPushConsumer;
+import org.apache.hc.core5.http.nio.HandlerFactory;
+
+final class NoopH2StreamHandler implements H2StreamHandler {
+
+    static final NoopH2StreamHandler INSTANCE = new NoopH2StreamHandler();
+
+    @Override
+    public boolean isOutputReady() {
+        return false;
+    }
+
+    @Override
+    public void produceOutput() throws HttpException, IOException {
+    }
+
+    @Override
+    public void consumePromise(final List<Header> headers) throws HttpException, IOException {
+    }
+
+    @Override
+    public void consumeHeader(final List<Header> headers, final boolean endStream) throws HttpException, IOException {
+    }
+
+    @Override
+    public void updateInputCapacity() throws IOException {
+    }
+
+    @Override
+    public void consumeData(final ByteBuffer src, final boolean endStream) throws HttpException, IOException {
+    }
+
+    @Override
+    public HandlerFactory<AsyncPushConsumer> getPushHandlerFactory() {
+        return null;
+    }
+
+    @Override
+    public void failed(final Exception cause) {
+    }
+
+    @Override
+    public void handle(final HttpException ex, final boolean endStream) throws HttpException, IOException {
+    }
+
+    @Override
+    public void releaseResources() {
+    }
+
+}

[httpcomponents-core] 02/02: H2 stream resource release optimization

Posted by ol...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch 5.1.x
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 06751c1f6713c5b0df5a3b1293484b0c45742714
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Wed Nov 10 12:01:58 2021 +0100

    H2 stream resource release optimization
---
 .../apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
index 885238f..dc73741 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/AbstractH2StreamMultiplexer.java
@@ -749,6 +749,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
                 if (stream.isTerminated()) {
                     streamMap.remove(streamId);
                     stream.releaseResources();
+                    requestSessionOutput();
                 }
             }
             break;
@@ -803,6 +804,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
                 if (stream.isTerminated()) {
                     streamMap.remove(streamId);
                     stream.releaseResources();
+                    requestSessionOutput();
                 }
             }
             break;
@@ -827,6 +829,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
                 if (stream.isTerminated()) {
                     streamMap.remove(streamId);
                     stream.releaseResources();
+                    requestSessionOutput();
                 }
             }
             break;
@@ -876,6 +879,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
                     stream.reset(new H2StreamResetException(errorCode, "Stream reset (" + errorCode + ")"));
                     streamMap.remove(streamId);
                     stream.releaseResources();
+                    requestSessionOutput();
                 }
             }
             break;
@@ -1207,6 +1211,7 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
             if (stream.isTerminated()) {
                 it.remove();
                 stream.releaseResources();
+                requestSessionOutput();
             }
             if (!outputQueue.isEmpty()) {
                 break;
@@ -1694,7 +1699,6 @@ abstract class AbstractH2StreamMultiplexer implements Identifiable, HttpConnecti
 
         void releaseResources() {
             handler.releaseResources();
-            channel.requestOutput();
         }
 
         void appendState(final StringBuilder buf) {