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

markt pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 47307ee27abcdea2ee40e33897aca760083de46a
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Oct 1 18:27:02 2024 +0100

    Split Stream.recycle() into replace() and recycle()
    
    Ensures recycle() is called no more than once.
    
    There are some error conditions that now no longer call recycle() to
    avoid the risk of duplicate calls to recycle if a Stream is being
    processed by a StreamProcessor at the same time Http2UpgradeHandler
    detects a Stream level error.
---
 .../apache/coyote/http2/LocalStrings.properties    |  3 +-
 java/org/apache/coyote/http2/Stream.java           | 50 ++++++++++++++++++----
 java/org/apache/coyote/http2/StreamProcessor.java  | 11 ++---
 webapps/docs/changelog.xml                         |  6 +++
 4 files changed, 55 insertions(+), 15 deletions(-)

diff --git a/java/org/apache/coyote/http2/LocalStrings.properties 
b/java/org/apache/coyote/http2/LocalStrings.properties
index 48c7ab33f6..e1fd352878 100644
--- a/java/org/apache/coyote/http2/LocalStrings.properties
+++ b/java/org/apache/coyote/http2/LocalStrings.properties
@@ -109,7 +109,8 @@ stream.inputBuffer.signal=Data added to inBuffer when read 
thread is waiting. Si
 stream.inputBuffer.swallowUnread=Swallowing [{0}] bytes previously read into 
input stream buffer
 stream.notWritable=Connection [{0}], Stream [{1}], This stream is not writable
 stream.outputBuffer.flush.debug=Connection [{0}], Stream [{1}], flushing 
output with buffer at position [{2}], writeInProgress [{3}] and closed [{4}]
-stream.recycle=Connection [{0}], Stream [{1}] has been recycled
+stream.recycle.duplicate=Connection [{0}], Stream [{1}] Duplicate request to 
recycle the associated request and response has been ignored
+stream.recycle.first=Connection [{0}], Stream [{1}] The associated request and 
response have been recycled
 stream.reset.fail=Connection [{0}], Stream [{1}], Failed to reset stream
 stream.reset.receive=Connection [{0}], Stream [{1}], Reset received due to 
[{2}]
 stream.reset.send=Connection [{0}], Stream [{1}], Reset sent due to [{2}]
diff --git a/java/org/apache/coyote/http2/Stream.java 
b/java/org/apache/coyote/http2/Stream.java
index 5ddb007d9f..0384c513fe 100644
--- a/java/org/apache/coyote/http2/Stream.java
+++ b/java/org/apache/coyote/http2/Stream.java
@@ -110,6 +110,9 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
     private volatile int urgency = Priority.DEFAULT_URGENCY;
     private volatile boolean incremental = Priority.DEFAULT_INCREMENTAL;
 
+    private final Object recycledLock = new Object();
+    private volatile boolean recycled = false;
+
 
     Stream(Integer identifier, Http2UpgradeHandler handler) {
         this(identifier, handler, null);
@@ -784,20 +787,15 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
         } else {
             handler.closeConnection(http2Exception);
         }
-        recycle();
+        replace();
     }
 
 
     /*
-     * This method is called recycle for consistency with the rest of the 
Tomcat code base. Currently, it calls the
-     * handler to replace this stream with an implementation that uses less 
memory. It does not fully recycle the Stream
-     * ready for re-use since Stream objects are not re-used. This is useful 
because Stream instances are retained for a
-     * period after the Stream closes.
+     * This method calls the handler to replace this stream with an 
implementation that uses less memory. This is useful
+     * because Stream instances are retained for a period after the Stream 
closes.
      */
-    final void recycle() {
-        if (log.isTraceEnabled()) {
-            log.trace(sm.getString("stream.recycle", getConnectionId(), 
getIdAsString()));
-        }
+    final void replace() {
         int remaining;
         // May be null if stream was closed before any DATA frames were 
processed.
         ByteBuffer inputByteBuffer = getInputByteBuffer(false);
@@ -807,6 +805,40 @@ class Stream extends AbstractNonZeroStream implements 
HeaderEmitter {
             remaining = inputByteBuffer.remaining();
         }
         handler.replaceStream(this, new RecycledStream(getConnectionId(), 
getIdentifier(), state, remaining));
+    }
+
+
+    /*
+     * This method is called recycle for consistency with the rest of the 
Tomcat code base. It does not recycle the
+     * Stream since Stream objects are not re-used. It does recycle the 
request and response objects and ensures that
+     * this is only done once.
+     *
+     * replace() should have been called before calling this method.
+     *
+     * It is important that this method is not called until any concurrent 
processing for the stream has completed. This
+     * is currently achieved by:
+     * - only the StreamProcessor calls this method
+     * - the Http2UpgradeHandler does not call this method
+     * - this method is called once the StreamProcessor considers the Stream 
closed
+     *
+     * In theory, the protection against duplicate calls is not required in 
this method (the code in StreamProcessor
+     * should be sufficient) but it is implemented as precaution along with 
the WARN level logging.
+     */
+    final void recycle() {
+        if (recycled) {
+            log.warn(sm.getString("stream.recycle.duplicate", 
getConnectionId(), getIdAsString()));
+            return;
+        }
+        synchronized (recycledLock) {
+            if (recycled) {
+                log.warn(sm.getString("stream.recycle.duplicate", 
getConnectionId(), getIdAsString()));
+                return;
+            }
+            recycled = true;
+        }
+        if (log.isTraceEnabled()) {
+            log.trace(sm.getString("stream.recycle.first", getConnectionId(), 
getIdAsString()));
+        }
         coyoteRequest.recycle();
         coyoteResponse.recycle();
         handler.getProtocol().pushRequestAndResponse(coyoteRequest);
diff --git a/java/org/apache/coyote/http2/StreamProcessor.java 
b/java/org/apache/coyote/http2/StreamProcessor.java
index e33c521b5a..cf33d5ba9a 100644
--- a/java/org/apache/coyote/http2/StreamProcessor.java
+++ b/java/org/apache/coyote/http2/StreamProcessor.java
@@ -87,9 +87,9 @@ class StreamProcessor extends AbstractProcessor {
             try {
                 /*
                  * In some scenarios, error handling may trigger multiple 
ERROR events for the same stream. The first
-                 * ERROR event process will close the stream and recycle it. 
Once the stream has been recycled it should
-                 * not be used for processing any further events. The check 
below ensures that this is the case. In
-                 * particular, Stream.recycle() should not be called more than 
once per Stream.
+                 * ERROR event processed will close the stream, replace it and 
recycle it. Once the stream has been
+                 * replaced it should not be used for processing any further 
events. When it is known that processing is
+                 * going to be a NO-OP, exit early.
                  */
                 if (!stream.equals(handler.getStream(stream.getIdAsInt()))) {
                     return;
@@ -130,8 +130,8 @@ class StreamProcessor extends AbstractProcessor {
                             stream.close(se);
                         } else {
                             if (!stream.isActive()) {
-                                // stream.close() will call recycle so only 
need it here
-                                stream.recycle();
+                                // Close calls replace() so need the same call 
here
+                                stream.replace();
                             }
                         }
                     }
@@ -146,6 +146,7 @@ class StreamProcessor extends AbstractProcessor {
                     state = SocketState.CLOSED;
                 } finally {
                     if (state == SocketState.CLOSED) {
+                        stream.recycle();
                         recycle();
                     }
                 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index fd9af8e75e..5e72d13411 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -164,6 +164,12 @@
         stream nor are trailer fields for an in progress stream swallowed if 
the
         Connector is paused before the trailer fields are received. (markt)
       </fix>
+      <fix>
+        Ensure the request and response are not recycled too soon for an HTTP/2
+        stream when a stream level error is detected during the processing of
+        incoming HTTP/2 frames. This could lead to incorrect processing times
+        appearing in the access log. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Jasper">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to