Author: markt
Date: Fri Feb  1 10:28:08 2019
New Revision: 1852699

URL: http://svn.apache.org/viewvc?rev=1852699&view=rev
Log:
Implement a write timeout for individual Streams

Added:
    tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Timeouts.java   (with 
props)
Modified:
    tomcat/trunk/java/org/apache/catalina/connector/OutputBuffer.java
    tomcat/trunk/java/org/apache/catalina/core/StandardWrapperValve.java
    tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties
    tomcat/trunk/java/org/apache/coyote/http2/Stream.java
    tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java
    tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java

Modified: tomcat/trunk/java/org/apache/catalina/connector/OutputBuffer.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/connector/OutputBuffer.java?rev=1852699&r1=1852698&r2=1852699&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/connector/OutputBuffer.java (original)
+++ tomcat/trunk/java/org/apache/catalina/connector/OutputBuffer.java Fri Feb  
1 10:28:08 2019
@@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletRes
 
 import org.apache.catalina.Globals;
 import org.apache.coyote.ActionCode;
+import org.apache.coyote.CloseNowException;
 import org.apache.coyote.Response;
 import org.apache.tomcat.util.buf.C2BConverter;
 import org.apache.tomcat.util.res.StringManager;
@@ -326,6 +327,13 @@ public class OutputBuffer extends Writer
             // real write to the adapter
             try {
                 coyoteResponse.doWrite(buf);
+            } catch (CloseNowException e) {
+                // Catch this sub-class as it requires specific handling.
+                // Examples where this exception is thrown:
+                // - HTTP/2 stream timeout
+                // Prevent further output for this response
+                closed = true;
+                throw e;
             } catch (IOException e) {
                 // An IOException on a write is almost always due to
                 // the remote client aborting the request. Wrap this

Modified: tomcat/trunk/java/org/apache/catalina/core/StandardWrapperValve.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/StandardWrapperValve.java?rev=1852699&r1=1852698&r2=1852699&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/core/StandardWrapperValve.java 
(original)
+++ tomcat/trunk/java/org/apache/catalina/core/StandardWrapperValve.java Fri 
Feb  1 10:28:08 2019
@@ -36,6 +36,7 @@ import org.apache.catalina.connector.Cli
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
 import org.apache.catalina.valves.ValveBase;
+import org.apache.coyote.CloseNowException;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.buf.MessageBytes;
 import org.apache.tomcat.util.log.SystemLogHandler;
@@ -201,7 +202,7 @@ final class StandardWrapperValve
                 }
 
             }
-        } catch (ClientAbortException e) {
+        } catch (ClientAbortException | CloseNowException e) {
             if (container.getLogger().isDebugEnabled()) {
                 container.getLogger().debug(sm.getString(
                         "standardWrapper.serviceException", wrapper.getName(),

Modified: tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties?rev=1852699&r1=1852698&r2=1852699&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties [UTF-8] 
(original)
+++ tomcat/trunk/java/org/apache/coyote/http2/LocalStrings.properties [UTF-8] 
Fri Feb  1 10:28:08 2019
@@ -100,6 +100,7 @@ stream.reset.fail=Connection [{0}], Stre
 stream.reset.receive=Connection [{0}], Stream [{1}], Reset received due to 
[{2}]
 stream.reset.send=Connection [{0}], Stream [{1}], Reset sent due to [{2}]
 stream.trailerHeader.noEndOfStream=Connection [{0}], Stream [{1}], The trailer 
headers did not include the end of stream flag
+stream.writeTimeout=Timeout waiting for client to increase flow control window 
to permit stream data to be written
 
 streamProcessor.error.connection=Connection [{0}], Stream [{1}], An error 
occurred during processing that was fatal to the connection
 streamProcessor.error.stream=Connection [{0}], Stream [{1}], An error occurred 
during processing that was fatal to the stream

Modified: tomcat/trunk/java/org/apache/coyote/http2/Stream.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Stream.java?rev=1852699&r1=1852698&r2=1852699&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Stream.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Stream.java Fri Feb  1 10:28:08 
2019
@@ -222,7 +222,21 @@ class Stream extends AbstractStream impl
             }
             try {
                 if (block) {
-                    wait();
+                    wait(handler.getProtocol().getStreamWriteTimeout());
+                    windowSize = getWindowSize();
+                    if (windowSize == 0) {
+                        String msg = sm.getString("stream.writeTimeout");
+                        StreamException se = new StreamException(
+                                msg, Http2Error.ENHANCE_YOUR_CALM, 
getIdAsInt());
+                        // Prevent the application making further writes
+                        streamOutputBuffer.closed = true;
+                        // Prevent Tomcat's error handling trying to write
+                        coyoteResponse.setError();
+                        coyoteResponse.setErrorReported();
+                        // Trigger a reset once control returns to Tomcat
+                        streamOutputBuffer.reset = se;
+                        throw new CloseNowException(msg, se);
+                    }
                 } else {
                     return 0;
                 }
@@ -232,7 +246,6 @@ class Stream extends AbstractStream impl
                 // Stream.
                 throw new IOException(e);
             }
-            windowSize = getWindowSize();
         }
         int allocation;
         if (windowSize < reservation) {
@@ -672,6 +685,11 @@ class Stream extends AbstractStream impl
     }
 
 
+    StreamException getResetException() {
+        return streamOutputBuffer.reset;
+    }
+
+
     private static void push(final Http2UpgradeHandler handler, final Request 
request,
             final Stream stream) throws IOException {
         if (org.apache.coyote.Constants.IS_SECURITY_ENABLED) {
@@ -724,6 +742,7 @@ class Stream extends AbstractStream impl
         private volatile long written = 0;
         private int streamReservation = 0;
         private volatile boolean closed = false;
+        private volatile StreamException reset = null;
         private volatile boolean endOfStreamSent = false;
 
         /* The write methods are synchronized to ensure that only one thread at
@@ -863,9 +882,14 @@ class Stream extends AbstractStream impl
 
         @Override
         public final void end() throws IOException {
-            closed = true;
-            flush(true);
-            writeTrailers();
+            if (reset != null) {
+                throw new CloseNowException(reset);
+            }
+            if (!closed) {
+                closed = true;
+                flush(true);
+                writeTrailers();
+            }
         }
 
         /**

Modified: tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java?rev=1852699&r1=1852698&r2=1852699&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/StreamProcessor.java Fri Feb  1 
10:28:08 2019
@@ -80,10 +80,13 @@ class StreamProcessor extends AbstractPr
                             log.info(ce.getMessage(), ce);
                             stream.close(ce);
                         } else if (!getErrorState().isIoAllowed()) {
-                            StreamException se = new 
StreamException(sm.getString(
-                                    "streamProcessor.error.stream", 
stream.getConnectionId(),
-                                    stream.getIdentifier()), 
Http2Error.INTERNAL_ERROR,
-                                    stream.getIdentifier().intValue());
+                            StreamException se = stream.getResetException();
+                            if (se == null) {
+                                se = new StreamException(sm.getString(
+                                        "streamProcessor.error.stream", 
stream.getConnectionId(),
+                                        stream.getIdentifier()), 
Http2Error.INTERNAL_ERROR,
+                                        stream.getIdentifier().intValue());
+                            }
                             stream.close(se);
                         }
                     }

Modified: tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java?rev=1852699&r1=1852698&r2=1852699&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java (original)
+++ tomcat/trunk/test/org/apache/coyote/http2/Http2TestBase.java Fri Feb  1 
10:28:08 2019
@@ -493,8 +493,10 @@ public abstract class Http2TestBase exte
         Http2Protocol http2Protocol = new Http2Protocol();
         // Short timeouts for now. May need to increase these for CI systems.
         http2Protocol.setReadTimeout(2000);
-        http2Protocol.setKeepAliveTimeout(5000);
         http2Protocol.setWriteTimeout(2000);
+        http2Protocol.setKeepAliveTimeout(5000);
+        http2Protocol.setStreamReadTimeout(1000);
+        http2Protocol.setStreamWriteTimeout(1000);
         http2Protocol.setMaxConcurrentStreams(maxConcurrentStreams);
         connector.addUpgradeProtocol(http2Protocol);
     }

Added: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Timeouts.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Timeouts.java?rev=1852699&view=auto
==============================================================================
--- tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Timeouts.java (added)
+++ tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Timeouts.java Fri Feb  1 
10:28:08 2019
@@ -0,0 +1,73 @@
+/*
+ *  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.
+ */
+package org.apache.coyote.http2;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestHttp2Timeouts extends Http2TestBase {
+
+    @Override
+    @Before
+    public void http2Connect() throws Exception {
+        super.http2Connect();
+        sendSettings(0, false, new 
SettingValue(Setting.INITIAL_WINDOW_SIZE.getId(), 0));
+    }
+
+
+    /*
+     * Simple request won't fill buffer so timeout will occur in Tomcat 
internal
+     * code during response completion.
+     */
+    @Test
+    public void testClientWithEmptyWindow() throws Exception {
+        sendSimpleGetRequest(3);
+
+        // Settings
+        parser.readFrame(false);
+        // Headers
+        parser.readFrame(false);
+
+        output.clearTrace();
+
+        parser.readFrame(false);
+        Assert.assertEquals("3-RST-[11]\n", output.getTrace());
+    }
+
+
+    /*
+     * Large request will fill buffer so timeout will occur in application code
+     * during response write (when Tomcat commits the response and flushes the
+     * buffer as a result of the buffer filling).
+     */
+    @Test
+    public void testClientWithEmptyWindowLargeResponse() throws Exception {
+        sendLargeGetRequest(3);
+
+        // Settings
+        parser.readFrame(false);
+        // Headers
+        parser.readFrame(false);
+
+        output.clearTrace();
+
+        parser.readFrame(false);
+        Assert.assertEquals("3-RST-[11]\n", output.getTrace());
+    }
+
+}

Propchange: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Timeouts.java
------------------------------------------------------------------------------
    svn:eol-style = native



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

Reply via email to