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