diff --git a/java/org/apache/coyote/http2/Http2Protocol.java b/java/org/apache/coyote/http2/Http2Protocol.java index b8a7d6fde68d..efc961ffce9b 100644 --- a/java/org/apache/coyote/http2/Http2Protocol.java +++ b/java/org/apache/coyote/http2/Http2Protocol.java @@ -112,6 +112,19 @@ public class Http2Protocol implements UpgradeProtocol { private boolean discardRequestsAndResponses = false; private final SynchronizedStack recycledRequestsAndResponses = new SynchronizedStack<>(); + /* + * Additional time in nanoseconds between sending the first graceful GOAWAY (max stream id) and the final GOAWAY + * (last seen stream id). During this time the server will continue to process new streams on the connection. + * This is to mitigate the race of client-buffered/sent packets for new streams and the final GOAWAY (with last + * seen stream id). By default, Tomcat uses the last computed RTT for this interval, but the RTT might have + * fluctuated due to network or server load conditions, or the client (e.g. nghttp2) might have already buffered + * frames for opening new streams on a connection. + * + * The name "drainTimeout" is taken from Envoy proxy's identical HTTP Connection Manager property and means exactly + * the same. + */ + private long drainTimeout; + @Override public String getHttpUpgradeName(boolean isSSLEnabled) { if (isSSLEnabled) { @@ -409,6 +422,16 @@ public void setDiscardRequestsAndResponses(boolean discardRequestsAndResponses) } + public long getDrainTimeout() { + return drainTimeout; + } + + + public void setDrainTimeout(long drainTimeout) { + this.drainTimeout = drainTimeout; + } + + Request popRequestAndResponse() { Request requestAndResponse = null; if (!discardRequestsAndResponses) { diff --git a/java/org/apache/coyote/http2/Http2UpgradeHandler.java b/java/org/apache/coyote/http2/Http2UpgradeHandler.java index 4ac67284e3bd..b84e2ca45bea 100644 --- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java +++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java @@ -140,6 +140,8 @@ class Http2UpgradeHandler extends AbstractStream implements InternalHttpUpgradeH private volatile int lastNonFinalDataPayload; private volatile int lastWindowUpdate; + // Time between the "graceful" GOAWAY (max stream id) and the final GOAWAY (last seen stream id) + private long drainTimeout = 0; Http2UpgradeHandler(Http2Protocol protocol, Adapter adapter, Request coyoteRequest, SocketWrapperBase socketWrapper) { @@ -173,6 +175,8 @@ class Http2UpgradeHandler extends AbstractStream implements InternalHttpUpgradeH pingManager.initiateDisabled = protocol.getInitiatePingDisabled(); + drainTimeout = protocol.getDrainTimeout(); + // Initial HTTP request becomes stream 1. if (coyoteRequest != null) { if (log.isTraceEnabled()) { @@ -543,7 +547,7 @@ public void destroy() { void checkPauseState() throws IOException { if (connectionState.get() == ConnectionState.PAUSING) { - if (pausedNanoTime + pingManager.getRoundTripTimeNano() < System.nanoTime()) { + if (pausedNanoTime + pingManager.getRoundTripTimeNano() + drainTimeout < System.nanoTime()) { connectionState.compareAndSet(ConnectionState.PAUSING, ConnectionState.PAUSED); writeGoAwayFrame(maxProcessedStreamId, Http2Error.NO_ERROR.getCode(), null); }