From 963d33111e5ba3d7239eddb869ca5497462b1963 Mon Sep 17 00:00:00 2001
From: Greg Wilkins
Date: Wed, 14 Jun 2023 09:57:10 +0200
Subject: [PATCH] Jetty 12 idletimeout (#9905)
* IdleTimeout review
+ pass TimeoutException through all APIs
+ HttpConnection now passes on TimeoutException to HttpChannel.onFailure
* More ServerTests for idletimeout
* Recreated a ServerTimeoutsTest for multiple transports
* more robust tests
Signed-off-by: Simone Bordet
* merged work from @sbordet and @gregw
* Various improvements to CyclicTimeouts.
* Improved reset of the earliest timeout before iteration.
* Removed check for getExpireNanoTime() == -1, since it's a valid value.
* When onExpired(Expirable) returns false, the Expirable should arrange to move its timeout in the future.
* fix keystore to please BoringSSL + use correct temp path
Signed-off-by: Ludovic Orban
* Fixed ErrorResponseAndCallback succeeded() and failed() to call super.failed() in all cases to complete the wrapped callback.
Signed-off-by: Simone Bordet
* Revert "Fixed ErrorResponseAndCallback succeeded() and failed() to call super.failed() in all cases to complete the wrapped callback."
This reverts commit 5ac57c13e013e62105ef7f0e86a29a420fb8cf2e.
* WIP idleTimeout
* WIP idleTimeout
* Added context wrapper for idle timeout listener
* updates from review
---------
Signed-off-by: Simone Bordet
Signed-off-by: Ludovic Orban
Co-authored-by: Simone Bordet
Co-authored-by: Ludovic Orban
---
.../org/eclipse/jetty/client/Socks4Proxy.java | 4 +-
.../org/eclipse/jetty/client/Socks5Proxy.java | 4 +-
.../internal/HttpConnectionOverHTTP.java | 4 +-
.../client/http/HttpReceiverOverHTTPTest.java | 5 +-
.../internal/HttpConnectionOverFCGI.java | 7 +-
.../server/internal/HttpStreamOverFCGI.java | 17 +-
.../server/internal/ServerFCGIConnection.java | 12 +-
.../jetty/fcgi/server/HttpClientTest.java | 4 +-
.../internal/ClientHTTP2StreamEndPoint.java | 8 +-
.../internal/HttpChannelOverHTTP2.java | 4 +-
.../internal/HttpReceiverOverHTTP2.java | 3 +-
.../org/eclipse/jetty/http2/HTTP2Channel.java | 5 +-
.../eclipse/jetty/http2/HTTP2Connection.java | 3 +-
.../org/eclipse/jetty/http2/HTTP2Stream.java | 2 +-
.../org/eclipse/jetty/http2/api/Stream.java | 5 +-
.../server/HTTP2ServerConnectionFactory.java | 2 +-
.../internal/HTTP2ServerConnection.java | 7 +-
.../server/internal/HttpStreamOverHTTP2.java | 17 +-
.../internal/ServerHTTP2StreamEndPoint.java | 13 +-
.../jetty/http2/tests/IdleTimeoutTest.java | 6 +-
.../jetty/http2/tests/RawHTTP2ProxyTest.java | 5 +-
.../jetty/http3/HTTP3StreamConnection.java | 6 +-
.../org/eclipse/jetty/http3/api/Stream.java | 3 +-
.../server/HTTP3ServerConnectionFactory.java | 5 +-
.../server/internal/HttpStreamOverHTTP3.java | 17 +-
.../internal/ServerHTTP3StreamConnection.java | 5 +-
.../http3/tests/StreamIdleTimeoutTest.java | 3 +-
.../eclipse/jetty/io/AbstractConnection.java | 8 +-
.../eclipse/jetty/io/AbstractEndPoint.java | 21 +-
.../java/org/eclipse/jetty/io/Connection.java | 3 +-
.../eclipse/jetty/io/ssl/SslConnection.java | 5 +-
.../jetty/io/ByteArrayEndPointTest.java | 34 +-
.../quic/client/ClientQuicConnection.java | 3 +-
.../jetty/quic/common/QuicConnection.java | 3 +-
.../quic/server/ServerQuicConnection.java | 3 +-
.../org/eclipse/jetty/server/HttpChannel.java | 19 +-
.../org/eclipse/jetty/server/HttpStream.java | 16 +
.../org/eclipse/jetty/server/Request.java | 49 ++-
.../jetty/server/handler/ContextHandler.java | 18 +
.../jetty/server/handler/ContextRequest.java | 18 +-
.../server/internal/HttpChannelState.java | 316 +++++++++++++-----
.../jetty/server/internal/HttpConnection.java | 24 ++
.../jetty/server/ConnectorTimeoutTest.java | 12 +-
.../eclipse/jetty/server/HttpChannelTest.java | 10 +-
.../jetty/server/HttpConnectionTest.java | 24 +-
.../eclipse/jetty/server/MockHttpStream.java | 11 +
.../org/eclipse/jetty/server/ServerTest.java | 207 +++++++++++-
.../jetty/session/TestableRequest.java | 10 +-
.../jetty-test-client-transports/pom.xml | 5 +
.../transport/HttpClientTimeoutTest.java | 2 +-
.../client/transport/ServerTimeoutsTest.java | 198 ++++++++++-
.../websocket/core/WebSocketConnection.java | 7 +-
.../jetty/ee10/servlet/DefaultServlet.java | 14 -
.../jetty/ee10/servlet/ServletChannel.java | 6 -
.../ee10/servlet/ServletContextRequest.java | 7 +
.../ee10/servlet/ServletRequestState.java | 13 +
.../test/client/transport/AbstractTest.java | 42 ++-
.../client/transport/ServerTimeoutsTest.java | 11 +-
.../src/test/resources/keystore.p12 | Bin 2597 -> 2774 bytes
.../eclipse/jetty/ee9/nested/HttpChannel.java | 4 -
.../eclipse/jetty/ee9/nested/RequestTest.java | 5 +-
.../jetty/ee9/nested/ResponseTest.java | 10 +-
62 files changed, 1016 insertions(+), 298 deletions(-)
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
index e2da92198b2b..855631099ffb 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Socks4Proxy.java
@@ -150,9 +150,9 @@ public void failed(Throwable x)
}
@Override
- public boolean onIdleExpired()
+ public boolean onIdleExpired(TimeoutException timeout)
{
- failed(new TimeoutException("Idle timeout expired"));
+ failed(timeout);
return false;
}
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Socks5Proxy.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Socks5Proxy.java
index b59b98881834..f4e353ec3b08 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Socks5Proxy.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Socks5Proxy.java
@@ -185,9 +185,9 @@ private void fail(Throwable x)
}
@Override
- public boolean onIdleExpired()
+ public boolean onIdleExpired(TimeoutException timeout)
{
- fail(new TimeoutException("Idle timeout expired"));
+ fail(timeout);
return false;
}
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpConnectionOverHTTP.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpConnectionOverHTTP.java
index d73db9336f55..6e7517a589a3 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpConnectionOverHTTP.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpConnectionOverHTTP.java
@@ -173,12 +173,12 @@ public Object getAttachment()
}
@Override
- public boolean onIdleExpired()
+ public boolean onIdleExpired(TimeoutException timeout)
{
long idleTimeout = getEndPoint().getIdleTimeout();
boolean close = onIdleTimeout(idleTimeout);
if (close)
- close(new TimeoutException("Idle timeout " + idleTimeout + " ms"));
+ close(timeout);
return false;
}
diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
index 21fe99bbfcca..45e28c94aba5 100644
--- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
+++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTPTest.java
@@ -45,6 +45,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.sameInstance;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -186,10 +187,12 @@ public void testReceiveResponseContentIdleTimeout(HttpCompliance compliance) thr
// ByteArrayEndPoint has an idle timeout of 0 by default,
// so to simulate an idle timeout is enough to wait a bit.
Thread.sleep(100);
- connection.onIdleExpired();
+ TimeoutException timeoutException = new TimeoutException();
+ connection.onIdleExpired(timeoutException);
ExecutionException e = assertThrows(ExecutionException.class, () -> listener.get(5, TimeUnit.SECONDS));
assertThat(e.getCause(), instanceOf(TimeoutException.class));
+ assertThat(e.getCause(), sameInstance(timeoutException));
}
@ParameterizedTest
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java
index fe807fd20cf3..4a28e87880c7 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java
@@ -217,13 +217,12 @@ private void shutdown()
}
@Override
- public boolean onIdleExpired()
+ public boolean onIdleExpired(TimeoutException timeoutException)
{
long idleTimeout = getEndPoint().getIdleTimeout();
- TimeoutException failure = new TimeoutException("Idle timeout " + idleTimeout + " ms");
- boolean close = delegate.onIdleTimeout(idleTimeout, failure);
+ boolean close = delegate.onIdleTimeout(idleTimeout, timeoutException);
if (close)
- close(failure);
+ close(timeoutException);
return false;
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java
index b5810a9d12db..83ae2a4f321f 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java
@@ -15,6 +15,7 @@
import java.nio.ByteBuffer;
import java.util.Locale;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.generator.Flusher;
@@ -301,6 +302,18 @@ private void generateResponseContent(ByteBufferPool.Accumulator accumulator, boo
_generator.generateResponseContent(accumulator, _id, buffer, last, _aborted);
}
+ @Override
+ public long getIdleTimeout()
+ {
+ return _connection.getEndPoint().getIdleTimeout();
+ }
+
+ @Override
+ public void setIdleTimeout(long idleTimeoutMs)
+ {
+ _connection.getEndPoint().setIdleTimeout(idleTimeoutMs);
+ }
+
@Override
public boolean isCommitted()
{
@@ -328,9 +341,9 @@ public void failed(Throwable x)
_connection.onCompleted(x);
}
- public boolean onIdleTimeout(Throwable timeout)
+ public boolean onIdleTimeout(TimeoutException timeout)
{
- Runnable task = _httpChannel.onFailure(timeout);
+ Runnable task = _httpChannel.onIdleTimeout(timeout);
if (task != null)
execute(task);
return false;
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java
index 439ae7352b05..166c536f8141 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java
@@ -16,6 +16,7 @@
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.Set;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.generator.Flusher;
@@ -291,7 +292,7 @@ private int fillInputBuffer()
}
@Override
- protected boolean onReadTimeout(Throwable timeout)
+ protected boolean onReadTimeout(TimeoutException timeout)
{
if (stream != null)
return stream.onIdleTimeout(timeout);
@@ -323,6 +324,15 @@ void onCompleted(Throwable failure)
getFlusher().shutdown();
}
+ @Override
+ public boolean onIdleExpired(TimeoutException timeoutException)
+ {
+ Runnable task = stream.getHttpChannel().onIdleTimeout(timeoutException);
+ if (task != null)
+ getExecutor().execute(task);
+ return false;
+ }
+
private class ServerListener implements ServerParser.Listener
{
@Override
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
index d705a00f8307..83c3a75cb550 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
@@ -522,6 +522,8 @@ public void testConnectionIdleTimeout() throws Exception
@Override
public boolean handle(org.eclipse.jetty.server.Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception
{
+ // Handler says it will handle the idletimeout
+ request.addIdleTimeoutListener(t -> false);
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
callback.succeeded();
return true;
@@ -530,7 +532,7 @@ public boolean handle(org.eclipse.jetty.server.Request request, org.eclipse.jett
connector.setIdleTimeout(idleTimeout);
- // Request does not fail because idle timeouts while dispatched are ignored.
+ // Request does not fail because handler says it will handle it.
ContentResponse response1 = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.idleTimeout(4 * idleTimeout, TimeUnit.MILLISECONDS)
diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/ClientHTTP2StreamEndPoint.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/ClientHTTP2StreamEndPoint.java
index c5c973d7bd0b..e5f47edb5d1a 100644
--- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/ClientHTTP2StreamEndPoint.java
+++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/ClientHTTP2StreamEndPoint.java
@@ -13,6 +13,8 @@
package org.eclipse.jetty.http2.client.transport.internal;
+import java.util.concurrent.TimeoutException;
+
import org.eclipse.jetty.http2.HTTP2Channel;
import org.eclipse.jetty.http2.HTTP2Stream;
import org.eclipse.jetty.http2.HTTP2StreamEndPoint;
@@ -38,13 +40,13 @@ public void onDataAvailable()
}
@Override
- public void onTimeout(Throwable failure, Promise promise)
+ public void onTimeout(TimeoutException timeout, Promise promise)
{
if (LOG.isDebugEnabled())
- LOG.debug("idle timeout on {}: {}", this, failure);
+ LOG.debug("idle timeout on {}", this, timeout);
Connection connection = getConnection();
if (connection != null)
- promise.succeeded(connection.onIdleExpired());
+ promise.succeeded(connection.onIdleExpired(timeout));
else
promise.succeeded(true);
}
diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpChannelOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpChannelOverHTTP2.java
index 961429b93e2f..61ea36249e87 100644
--- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpChannelOverHTTP2.java
+++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpChannelOverHTTP2.java
@@ -13,6 +13,8 @@
package org.eclipse.jetty.http2.client.transport.internal;
+import java.util.concurrent.TimeoutException;
+
import org.eclipse.jetty.client.Result;
import org.eclipse.jetty.client.transport.HttpChannel;
import org.eclipse.jetty.client.transport.HttpExchange;
@@ -200,7 +202,7 @@ public void onReset(Stream stream, ResetFrame frame, Callback callback)
}
@Override
- public void onIdleTimeout(Stream stream, Throwable x, Promise promise)
+ public void onIdleTimeout(Stream stream, TimeoutException x, Promise promise)
{
HTTP2Channel.Client channel = (HTTP2Channel.Client)((HTTP2Stream)stream).getAttachment();
channel.onTimeout(x, promise);
diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpReceiverOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpReceiverOverHTTP2.java
index 6fee831c31d3..153f64d2c897 100644
--- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpReceiverOverHTTP2.java
+++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpReceiverOverHTTP2.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.http2.client.transport.internal;
import java.io.IOException;
+import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;
import org.eclipse.jetty.client.HttpUpgrader;
@@ -220,7 +221,7 @@ void onReset(ResetFrame frame)
}
@Override
- public void onTimeout(Throwable failure, Promise promise)
+ public void onTimeout(TimeoutException failure, Promise promise)
{
HttpExchange exchange = getHttpExchange();
if (exchange != null)
diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java
index df5d1b23b672..49c682362af3 100644
--- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java
+++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Channel.java
@@ -13,6 +13,7 @@
package org.eclipse.jetty.http2;
+import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import org.eclipse.jetty.http2.frames.HeadersFrame;
@@ -34,7 +35,7 @@ public interface Client
{
public void onDataAvailable();
- public void onTimeout(Throwable failure, Promise promise);
+ public void onTimeout(TimeoutException failure, Promise promise);
public void onFailure(Throwable failure, Callback callback);
}
@@ -54,7 +55,7 @@ public interface Server
// TODO: review the signature because the serialization done by HttpChannel.onError()
// is now failing the callback which fails the HttpStream, which should decide whether
// to reset the HTTP/2 stream, so we may not need the boolean return type.
- public void onTimeout(Throwable failure, BiConsumer consumer);
+ public void onTimeout(TimeoutException timeout, BiConsumer consumer);
// TODO: can it be simplified? The callback seems to only be succeeded, which
// means it can be converted into a Runnable which may just be the return type
diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java
index 97a8f1379b18..20e6b36350c1 100644
--- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java
+++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java
@@ -18,6 +18,7 @@
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.http2.api.Stream;
@@ -174,7 +175,7 @@ private int fill(EndPoint endPoint, ByteBuffer buffer)
}
@Override
- public boolean onIdleExpired()
+ public boolean onIdleExpired(TimeoutException timeoutException)
{
boolean idle = isFillInterested();
if (idle)
diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java
index f992f2fc0de0..75b447c278cb 100644
--- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java
+++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java
@@ -839,7 +839,7 @@ private void notifyReset(Stream stream, ResetFrame frame, Callback callback)
}
}
- private void notifyIdleTimeout(Stream stream, Throwable failure, Promise promise)
+ private void notifyIdleTimeout(Stream stream, TimeoutException failure, Promise promise)
{
Listener listener = this.listener;
if (listener != null)
diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java
index 8df803d05b0a..dc778c75cb1b 100644
--- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java
+++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/api/Stream.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.http2.api;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
@@ -218,7 +219,7 @@ public default CompletableFuture reset(ResetFrame frame)
/**
* @param idleTimeout the stream idle timeout
* @see #getIdleTimeout()
- * @see Stream.Listener#onIdleTimeout(Stream, Throwable, Promise)
+ * @see Stream.Listener#onIdleTimeout(Stream, TimeoutException, Promise)
*/
public void setIdleTimeout(long idleTimeout);
@@ -369,7 +370,7 @@ public default void onReset(Stream stream, ResetFrame frame, Callback callback)
* @param promise the promise to complete
* @see #getIdleTimeout()
*/
- public default void onIdleTimeout(Stream stream, Throwable x, Promise promise)
+ public default void onIdleTimeout(Stream stream, TimeoutException x, Promise promise)
{
promise.succeeded(true);
}
diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java
index ac99a6f14e47..0bb7517c9ec7 100644
--- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java
+++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java
@@ -168,7 +168,7 @@ private void onFailure(Stream stream, Throwable failure, Callback callback)
}
@Override
- public void onIdleTimeout(Stream stream, Throwable x, Promise promise)
+ public void onIdleTimeout(Stream stream, TimeoutException x, Promise promise)
{
getConnection().onStreamTimeout(stream, x, promise);
}
diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java
index acbe10e7debe..289bc28a503a 100644
--- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java
+++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HTTP2ServerConnection.java
@@ -19,6 +19,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
@@ -155,14 +156,14 @@ public void onTrailers(Stream stream, HeadersFrame frame)
}
}
- public void onStreamTimeout(Stream stream, Throwable failure, Promise promise)
+ public void onStreamTimeout(Stream stream, TimeoutException timeout, Promise promise)
{
if (LOG.isDebugEnabled())
- LOG.debug("Idle timeout on {}", stream, failure);
+ LOG.debug("Idle timeout on {}", stream, timeout);
HTTP2Channel.Server channel = (HTTP2Channel.Server)((HTTP2Stream)stream).getAttachment();
if (channel != null)
{
- channel.onTimeout(failure, (task, timedOut) ->
+ channel.onTimeout(timeout, (task, timedOut) ->
{
if (task != null)
offerTask(task, true);
diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java
index b98d5981d14d..f0b5dbb5f72b 100644
--- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java
+++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/HttpStreamOverHTTP2.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.http2.server.internal;
import java.nio.ByteBuffer;
+import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -451,6 +452,18 @@ private boolean isTunnel(MetaData.Request request, MetaData.Response response)
return MetaData.isTunnel(request.getMethod(), response.getStatus());
}
+ @Override
+ public long getIdleTimeout()
+ {
+ return _stream.getIdleTimeout();
+ }
+
+ @Override
+ public void setIdleTimeout(long idleTimeoutMs)
+ {
+ _stream.setIdleTimeout(idleTimeoutMs);
+ }
+
@Override
public void push(MetaData.Request resource)
{
@@ -558,9 +571,9 @@ public Throwable consumeAvailable()
}
@Override
- public void onTimeout(Throwable failure, BiConsumer consumer)
+ public void onTimeout(TimeoutException timeout, BiConsumer consumer)
{
- Runnable task = _httpChannel.onFailure(failure);
+ Runnable task = _httpChannel.onIdleTimeout(timeout);
boolean idle = !_httpChannel.isRequestHandled();
consumer.accept(task, idle);
}
diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java
index bee4bf12a5d5..8f285557f288 100644
--- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java
+++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/internal/ServerHTTP2StreamEndPoint.java
@@ -13,6 +13,7 @@
package org.eclipse.jetty.http2.server.internal;
+import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import org.eclipse.jetty.http2.HTTP2Channel;
@@ -48,19 +49,19 @@ public Runnable onTrailer(HeadersFrame frame)
}
@Override
- public void onTimeout(Throwable failure, BiConsumer consumer)
+ public void onTimeout(TimeoutException timeout, BiConsumer consumer)
{
if (LOG.isDebugEnabled())
- LOG.debug("idle timeout on {}: {}", this, failure);
+ LOG.debug("idle timeout on {}", this, timeout);
boolean result = true;
Connection connection = getConnection();
if (connection != null)
- result = connection.onIdleExpired();
+ result = connection.onIdleExpired(timeout);
Runnable r = null;
if (result)
{
- processFailure(failure);
- r = () -> close(failure);
+ processFailure(timeout);
+ r = () -> close(timeout);
}
consumer.accept(r, result);
}
@@ -69,7 +70,7 @@ public void onTimeout(Throwable failure, BiConsumer consumer)
public Runnable onFailure(Throwable failure, Callback callback)
{
if (LOG.isDebugEnabled())
- LOG.debug("failure on {}: {}", this, failure);
+ LOG.debug("failure on {}", this, failure);
processFailure(failure);
close(failure);
return callback::succeeded;
diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java
index bd054472e97c..f12aa6e45573 100644
--- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java
+++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/IdleTimeoutTest.java
@@ -392,7 +392,7 @@ public void onDataAvailable(Stream stream)
}
@Override
- public void onIdleTimeout(Stream stream, Throwable x, Promise promise)
+ public void onIdleTimeout(Stream stream, TimeoutException x, Promise promise)
{
assertThat(x, Matchers.instanceOf(TimeoutException.class));
timeoutLatch.countDown();
@@ -429,7 +429,7 @@ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
return new Stream.Listener()
{
@Override
- public void onIdleTimeout(Stream stream, Throwable x, Promise promise)
+ public void onIdleTimeout(Stream stream, TimeoutException x, Promise promise)
{
timeoutLatch.countDown();
promise.succeeded(true);
@@ -476,7 +476,7 @@ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
return new Stream.Listener()
{
@Override
- public void onIdleTimeout(Stream stream, Throwable x, Promise promise)
+ public void onIdleTimeout(Stream stream, TimeoutException x, Promise promise)
{
timeoutLatch.countDown();
promise.succeeded(true);
diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java
index 80f88d46b41e..e8db27522da8 100644
--- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java
+++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/RawHTTP2ProxyTest.java
@@ -25,6 +25,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
@@ -521,7 +522,7 @@ public void onReset(Stream stream, ResetFrame frame, Callback callback)
}
@Override
- public void onIdleTimeout(Stream stream, Throwable x, Promise promise)
+ public void onIdleTimeout(Stream stream, TimeoutException x, Promise promise)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("CPS idle timeout for {}", stream);
@@ -684,7 +685,7 @@ public void onReset(Stream stream, ResetFrame frame, Callback callback)
}
@Override
- public void onIdleTimeout(Stream stream, Throwable x, Promise promise)
+ public void onIdleTimeout(Stream stream, TimeoutException x, Promise promise)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("SPC idle timeout for {}", stream);
diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java
index a69ca91e31cd..a2917482d52a 100644
--- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java
+++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/HTTP3StreamConnection.java
@@ -17,6 +17,7 @@
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.HttpStatus;
@@ -84,7 +85,7 @@ public void onOpen()
}
@Override
- protected boolean onReadTimeout(Throwable timeout)
+ protected boolean onReadTimeout(TimeoutException timeout)
{
// Idle timeouts are handled by HTTP3Stream.
return false;
@@ -476,7 +477,8 @@ public void onData(long streamId, DataFrame frame)
if (LOG.isDebugEnabled())
LOG.debug("received {}#{}", frame, streamId);
Runnable delegate = () -> super.onData(streamId, frame);
- if (!HTTP3StreamConnection.this.action.compareAndSet(null, () -> processData(frame, delegate)))
+ Runnable action = () -> processData(frame, delegate);
+ if (!HTTP3StreamConnection.this.action.compareAndSet(null, action))
throw new IllegalStateException();
}
}
diff --git a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java
index 2b1a2c8126e0..8a078c62f35e 100644
--- a/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java
+++ b/jetty-core/jetty-http3/jetty-http3-common/src/main/java/org/eclipse/jetty/http3/api/Stream.java
@@ -16,6 +16,7 @@
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http3.frames.DataFrame;
@@ -354,7 +355,7 @@ public default void onTrailer(Stream.Server stream, HeadersFrame frame)
* @param promise the promise to complete with true to reset the stream,
* false to ignore the idle timeout
*/
- public default void onIdleTimeout(Server stream, Throwable failure, Promise promise)
+ public default void onIdleTimeout(Server stream, TimeoutException failure, Promise promise)
{
promise.succeeded(true);
}
diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java
index a9ebfe1743d4..42256031e64f 100644
--- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java
+++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/HTTP3ServerConnectionFactory.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.http3.server;
import java.util.Objects;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpVersion;
@@ -142,10 +143,10 @@ public void onTrailer(Stream.Server stream, HeadersFrame frame)
}
@Override
- public void onIdleTimeout(Stream.Server stream, Throwable failure, Promise promise)
+ public void onIdleTimeout(Stream.Server stream, TimeoutException timeout, Promise promise)
{
HTTP3Stream http3Stream = (HTTP3Stream)stream;
- getConnection().onIdleTimeout((HTTP3Stream)stream, failure, (task, timedOut) ->
+ getConnection().onIdleTimeout((HTTP3Stream)stream, timeout, (task, timedOut) ->
{
if (task != null)
{
diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java
index 0cbead3f8d66..e392218d844e 100644
--- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java
+++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/HttpStreamOverHTTP3.java
@@ -16,6 +16,7 @@
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -471,6 +472,18 @@ private CompletableFuture sendTrailerFrame(HttpFields trailers)
return stream.trailer(frame);
}
+ @Override
+ public long getIdleTimeout()
+ {
+ return stream.getIdleTimeout();
+ }
+
+ @Override
+ public void setIdleTimeout(long idleTimeoutMs)
+ {
+ stream.setIdleTimeout(idleTimeoutMs);
+ }
+
@Override
public boolean isCommitted()
{
@@ -514,9 +527,9 @@ public void failed(Throwable x)
stream.reset(HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), x);
}
- public void onIdleTimeout(Throwable failure, BiConsumer consumer)
+ public void onIdleTimeout(TimeoutException failure, BiConsumer consumer)
{
- Runnable runnable = httpChannel.onFailure(failure);
+ Runnable runnable = httpChannel.onIdleTimeout(failure);
boolean idle = !httpChannel.isRequestHandled();
consumer.accept(runnable, idle);
}
diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3StreamConnection.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3StreamConnection.java
index 3ff29e4b2e23..754948318ab1 100644
--- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3StreamConnection.java
+++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3StreamConnection.java
@@ -16,6 +16,7 @@
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Set;
+import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import org.eclipse.jetty.http.HttpVersion;
@@ -69,10 +70,10 @@ public Runnable onTrailer(HTTP3Stream stream, HeadersFrame frame)
return httpStream.onTrailer(frame);
}
- public void onIdleTimeout(HTTP3Stream stream, Throwable failure, BiConsumer consumer)
+ public void onIdleTimeout(HTTP3Stream stream, TimeoutException timeout, BiConsumer consumer)
{
HttpStreamOverHTTP3 httpStream = (HttpStreamOverHTTP3)stream.getAttachment();
- httpStream.onIdleTimeout(failure, consumer);
+ httpStream.onIdleTimeout(timeout, consumer);
}
public Runnable onFailure(HTTP3Stream stream, Throwable failure)
diff --git a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/StreamIdleTimeoutTest.java b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/StreamIdleTimeoutTest.java
index 4b109fe7d158..cf46d35236ce 100644
--- a/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/StreamIdleTimeoutTest.java
+++ b/jetty-core/jetty-http3/jetty-http3-tests/src/test/java/org/eclipse/jetty/http3/tests/StreamIdleTimeoutTest.java
@@ -16,6 +16,7 @@
import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.HttpFields;
@@ -157,7 +158,7 @@ public Stream.Server.Listener onRequest(Stream.Server stream, HeadersFrame frame
return new Stream.Server.Listener()
{
@Override
- public void onIdleTimeout(Stream.Server stream, Throwable failure, Promise promise)
+ public void onIdleTimeout(Stream.Server stream, TimeoutException failure, Promise promise)
{
serverIdleLatch.countDown();
promise.succeeded(true);
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
index dcfb9cb3c88f..3f40f11c3902 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java
@@ -174,8 +174,8 @@ protected void onFillInterestedFailed(Throwable cause)
if (_endPoint.isOpen())
{
boolean close = true;
- if (cause instanceof TimeoutException)
- close = onReadTimeout(cause);
+ if (cause instanceof TimeoutException timeout)
+ close = onReadTimeout(timeout);
if (close)
{
if (_endPoint.isOutputShutdown())
@@ -195,7 +195,7 @@ protected void onFillInterestedFailed(Throwable cause)
* @param timeout the cause of the read timeout
* @return true to signal that the endpoint must be closed, false to keep the endpoint open
*/
- protected boolean onReadTimeout(Throwable timeout)
+ protected boolean onReadTimeout(TimeoutException timeout)
{
return true;
}
@@ -268,7 +268,7 @@ public void close()
}
@Override
- public boolean onIdleExpired()
+ public boolean onIdleExpired(TimeoutException timeoutException)
{
return true;
}
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
index 48e726a80ca9..894ccf08344d 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java
@@ -392,30 +392,27 @@ public WriteFlusher getWriteFlusher()
protected void onIdleExpired(TimeoutException timeout)
{
Connection connection = _connection;
- if (connection != null && !connection.onIdleExpired())
+ if (connection != null && !connection.onIdleExpired(timeout))
return;
boolean outputShutdown = isOutputShutdown();
boolean inputShutdown = isInputShutdown();
boolean fillFailed = _fillInterest.onFail(timeout);
boolean writeFailed = _writeFlusher.onFail(timeout);
+ boolean isOpen = isOpen();
- // If the endpoint is half closed and there was no fill/write handling, then close here.
- // This handles the situation where the connection has completed its close handling
- // and the endpoint is half closed, but the other party does not complete the close.
- // This perhaps should not check for half closed, however the servlet spec case allows
- // for a dispatched servlet or suspended request to extend beyond the connections idle
- // time. So if this test would always close an idle endpoint that is not handled, then
- // we would need a mode to ignore timeouts for some HTTP states
- if (isOpen() && (inputShutdown || outputShutdown) && !(fillFailed || writeFailed))
- close();
- else
- LOG.debug("handled idle inputShutdown={} outputShutdown={} fillFailed={} writeFailed={} for {}",
+ if (LOG.isDebugEnabled())
+ LOG.debug("handled idle isOpen={} inputShutdown={} outputShutdown={} fillFailed={} writeFailed={} for {}",
+ isOpen,
inputShutdown,
outputShutdown,
fillFailed,
writeFailed,
this);
+
+ // If the endpoint is open and there was no fill/write handling, then close here.
+ if (isOpen && !(fillFailed || writeFailed))
+ close(timeout);
}
@Override
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java
index 52b202009c48..edbd16f20580 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java
@@ -16,6 +16,7 @@
import java.io.Closeable;
import java.nio.ByteBuffer;
import java.util.EventListener;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.util.component.Container;
@@ -82,7 +83,7 @@ public interface Connection extends Closeable
* @return true to let the EndPoint handle the idle timeout,
* false to tell the EndPoint to halt the handling of the idle timeout.
*/
- boolean onIdleExpired();
+ boolean onIdleExpired(TimeoutException timeoutException);
long getMessagesIn();
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
index ad2d1a8fc160..6f54876226fb 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
@@ -19,6 +19,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.ToIntFunction;
@@ -348,9 +349,9 @@ public void close()
}
@Override
- public boolean onIdleExpired()
+ public boolean onIdleExpired(TimeoutException timeoutException)
{
- return getSslEndPoint().getConnection().onIdleExpired();
+ return getSslEndPoint().getConnection().onIdleExpired(timeoutException);
}
@Override
diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java
index fa6668e13a51..cb4bb5f1a53f 100644
--- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java
+++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java
@@ -17,11 +17,9 @@
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.FutureCallback;
-import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.jupiter.api.AfterEach;
@@ -30,9 +28,6 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -270,33 +265,6 @@ public void testIdle() throws Exception
assertTrue(endp.isOpen());
Thread.sleep(oneAndHalfIdleTimeout);
- // Still open because it has not been oshut or closed explicitly
- // and there are no callbacks, so idle timeout is ignored.
- assertTrue(endp.isOpen());
-
- // Normal read is immediate, since there is data to read.
- ByteBuffer buffer = BufferUtil.allocate(1024);
- FutureCallback fcb = new FutureCallback();
- endp.fillInterested(fcb);
- fcb.get(idleTimeout, TimeUnit.MILLISECONDS);
- assertTrue(fcb.isDone());
- assertEquals(4, endp.fill(buffer));
- assertEquals("test", BufferUtil.toString(buffer));
-
- // Wait for a read timeout.
- long start = NanoTime.now();
- fcb = new FutureCallback();
- endp.fillInterested(fcb);
- try
- {
- fcb.get();
- fail("Expected ExecutionException");
- }
- catch (ExecutionException t)
- {
- assertThat(t.getCause(), instanceOf(TimeoutException.class));
- }
- assertThat(NanoTime.millisSince(start), greaterThan(halfIdleTimeout));
- assertThat("Endpoint open", endp.isOpen(), is(true));
+ assertFalse(endp.isOpen());
}
}
diff --git a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java
index 3edb7e493d65..bd0e307cb788 100644
--- a/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java
+++ b/jetty-core/jetty-quic/jetty-quic-client/src/main/java/org/eclipse/jetty/quic/client/ClientQuicConnection.java
@@ -23,6 +23,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.DatagramChannelEndPoint;
@@ -159,7 +160,7 @@ protected void onFailure(Throwable failure)
}
@Override
- public boolean onIdleExpired()
+ public boolean onIdleExpired(TimeoutException timeoutException)
{
boolean idle = isFillInterested();
long idleTimeout = getEndPoint().getIdleTimeout();
diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java
index 37693a98f843..0b324ec8a870 100644
--- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java
+++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicConnection.java
@@ -24,6 +24,7 @@
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.io.AbstractConnection;
@@ -171,7 +172,7 @@ public void fillInterested()
}
@Override
- public abstract boolean onIdleExpired();
+ public abstract boolean onIdleExpired(TimeoutException timeoutException);
@Override
public void close()
diff --git a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java
index 01a1a8b34edc..988a24053171 100644
--- a/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java
+++ b/jetty-core/jetty-quic/jetty-quic-server/src/main/java/org/eclipse/jetty/quic/server/ServerQuicConnection.java
@@ -18,6 +18,7 @@
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.Iterator;
+import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.CyclicTimeouts;
@@ -99,7 +100,7 @@ public void schedule(ServerQuicSession session)
}
@Override
- public boolean onIdleExpired()
+ public boolean onIdleExpired(TimeoutException timeoutException)
{
// The current server architecture only has one listening
// DatagramChannelEndPoint, so we ignore idle timeouts.
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
index 31a9b67af218..3d1d215e21d3 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
@@ -13,6 +13,10 @@
package org.eclipse.jetty.server;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.util.thread.Invocable;
@@ -70,14 +74,25 @@ public interface HttpChannel extends Invocable
*/
Runnable onContentAvailable();
+ /**
+ * Notifies this {@code HttpChannel} that an idle timeout happened.
+ *
+ * @param idleTimeout the timeout.
+ * @return a {@code Runnable} that performs the timeout action, or {@code null}
+ * if no action need be performed by the calling thread
+ * @see Request#addIdleTimeoutListener(Predicate)
+ */
+ Runnable onIdleTimeout(TimeoutException idleTimeout);
+
/**
* Notifies this {@code HttpChannel} that an asynchronous failure happened.
- * Typical failure examples could be idle timeouts, I/O read failures or
+ *
Typical failure examples could be HTTP/2 resets or
* protocol failures (for example, invalid request bytes).
*
* @param failure the failure cause.
* @return a {@code Runnable} that performs the failure action, or {@code null}
- * if no failure action should be performed by the caller thread
+ * if no failure action need be performed by the calling thread
+ * @see Request#addFailureListener(Consumer)
*/
Runnable onFailure(Throwable failure);
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpStream.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpStream.java
index ee26a94278eb..99026178623f 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpStream.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpStream.java
@@ -99,6 +99,10 @@ default void push(MetaData.Request resource)
throw new UnsupportedOperationException();
}
+ long getIdleTimeout();
+
+ void setIdleTimeout(long idleTimeoutMs);
+
boolean isCommitted();
default TunnelSupport getTunnelSupport()
@@ -192,6 +196,18 @@ public void push(MetaData.Request resource)
getWrapped().push(resource);
}
+ @Override
+ public long getIdleTimeout()
+ {
+ return getWrapped().getIdleTimeout();
+ }
+
+ @Override
+ public void setIdleTimeout(long idleTimeoutMs)
+ {
+ getWrapped().setIdleTimeout(idleTimeoutMs);
+ }
+
@Override
public final boolean isCommitted()
{
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
index fec4c56d6fc5..cf0fb744492c 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -22,6 +22,8 @@
import java.security.Principal;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -249,18 +251,41 @@ default void push(MetaData.Request resource)
}
/**
- * Adds a listener for asynchronous errors.
+ * Adds a listener for idle timeouts.
* The listener is a predicate function that should return {@code true} to indicate
- * that the function will complete (either successfully or with a failure) the callback
- * received from {@link org.eclipse.jetty.server.Handler#handle(Request, Response, Callback)}, or
- * {@code false} otherwise.
+ * that the idle timeout should be handled by the container as a hard failure
+ * (see {@link #addFailureListener(Consumer)}); or {@code false} to ignore that specific timeout and for another timeout
+ * to occur after another idle period.
+ * Any pending {@link #demand(Runnable)} or {@link Response#write(boolean, ByteBuffer, Callback)} operations
+ * are not affected by this call. Applications need to be mindful of any such pending operations if attempting
+ * to make new operations.
* Listeners are processed in sequence, and the first that returns {@code true}
* stops the processing of subsequent listeners, which are therefore not invoked.
*
- * @param onError the predicate function
- * @return true if the listener completes the callback, false otherwise
+ * @param onIdleTimeout the predicate function
+ * @see #addFailureListener(Consumer)
*/
- boolean addErrorListener(Predicate onError);
+ void addIdleTimeoutListener(Predicate onIdleTimeout);
+
+ /**
+ * Adds a listener for asynchronous hard errors.
+ * When a listener is called, the effects of the error will already have taken place:
+ *
+ * - Pending {@link #demand(Runnable)} will be woken up.
+ * - Calls to {@link #read()} will return the {@code Throwable}.
+ * - Pending and new {@link Response#write(boolean, ByteBuffer, Callback)} calls will be failed by
+ * calling {@link Callback#failed(Throwable)} on the callback passed to {@code write(...)}.
+ * - Any call to {@link Callback#succeeded()} on the callback passed to
+ * {@link Handler#handle(Request, Response, Callback)} will effectively be a call to {@link Callback#failed(Throwable)}
+ * with the notified {@link Throwable}.
+ *
+ * Listeners are processed in sequence. When all listeners are invoked then {@link Callback#failed(Throwable)}
+ * will be called on the callback passed to {@link Handler#handle(Request, Response, Callback)}.
+ *
+ * @param onFailure the consumer function
+ * @see #addIdleTimeoutListener(Predicate)
+ */
+ void addFailureListener(Consumer onFailure);
TunnelSupport getTunnelSupport();
@@ -657,9 +682,15 @@ public void push(MetaData.Request resource)
}
@Override
- public boolean addErrorListener(Predicate onError)
+ public void addIdleTimeoutListener(Predicate onIdleTimeout)
+ {
+ getWrapped().addIdleTimeoutListener(onIdleTimeout);
+ }
+
+ @Override
+ public void addFailureListener(Consumer onFailure)
{
- return getWrapped().addErrorListener(onError);
+ getWrapped().addFailureListener(onFailure);
}
@Override
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
index ced33a0e0e2a..26c9ef7ab81b 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -28,6 +28,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.eclipse.jetty.http.HttpField;
@@ -1202,6 +1203,23 @@ public void call(Invocable.Callable callable, Request request) throws Exception
}
}
+ public boolean test(Predicate predicate, T t, Request request)
+ {
+ Context lastContext = __context.get();
+ if (lastContext == this)
+ return predicate.test(t);
+
+ ClassLoader lastLoader = enterScope(request);
+ try
+ {
+ return predicate.test(t);
+ }
+ finally
+ {
+ exitScope(request, lastContext, lastLoader);
+ }
+ }
+
public void accept(Consumer consumer, Throwable t, Request request)
{
Context lastContext = __context.get();
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextRequest.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextRequest.java
index ba02bfec1539..1a4eb4b30196 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextRequest.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextRequest.java
@@ -13,6 +13,8 @@
package org.eclipse.jetty.server.handler;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import org.eclipse.jetty.server.Context;
@@ -40,15 +42,15 @@ public void demand(Runnable demandCallback)
}
@Override
- public boolean addErrorListener(Predicate onError)
+ public void addIdleTimeoutListener(Predicate onIdleTimeout)
{
- return super.addErrorListener(t ->
- {
- // TODO: implement the line below
- // return _context.apply(onError::test, t, ContextRequest.this);
- _context.accept(onError::test, t, ContextRequest.this);
- return true;
- });
+ super.addIdleTimeoutListener(t -> _context.test(onIdleTimeout, t, ContextRequest.this));
+ }
+
+ @Override
+ public void addFailureListener(Consumer onFailure)
+ {
+ super.addFailureListener(t -> _context.accept(onFailure, t, ContextRequest.this));
}
@Override
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
index 74b9e9f34680..c6e7ab8cf815 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpChannelState.java
@@ -19,7 +19,9 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.LongAdder;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -110,12 +112,23 @@ private enum StreamSendState
private boolean _callbackCompleted = false;
private ChannelRequest _request;
private ChannelResponse _response;
+ private long _oldIdleTimeout;
private HttpStream _stream;
private long _committedContentLength = -1;
private Runnable _onContentAvailable;
- private Content.Chunk.Error _error;
- private Throwable _failure;
- private Predicate _onError;
+ private Predicate _onIdleTimeout;
+ /**
+ * Failure passed to {@link #onFailure(Throwable)}
+ */
+ private Content.Chunk.Error _failure;
+ /**
+ * Listener for {@link #onFailure(Throwable)} events
+ */
+ private Consumer _onFailure;
+ /**
+ * Failure passed to {@link ChannelCallback#failed(Throwable)}
+ */
+ private Throwable _callbackFailure;
private Attributes _cache;
public HttpChannelState(ConnectionMetaData connectionMetaData)
@@ -149,11 +162,11 @@ public void recycle()
_handling = null;
_handled = false;
_callbackCompleted = false;
- _failure = null;
+ _callbackFailure = null;
_committedContentLength = -1;
_onContentAvailable = null;
- _error = null;
- _onError = null;
+ _failure = null;
+ _onFailure = null;
}
}
@@ -262,13 +275,20 @@ public Runnable onRequest(MetaData.Request request)
_response = new ChannelResponse(_request);
HttpFields.Mutable responseHeaders = _response.getHeaders();
- if (getHttpConfiguration().getSendServerVersion())
+ HttpConfiguration httpConfiguration = getHttpConfiguration();
+ if (httpConfiguration.getSendServerVersion())
responseHeaders.add(SERVER_VERSION);
- if (getHttpConfiguration().getSendXPoweredBy())
+ if (httpConfiguration.getSendXPoweredBy())
responseHeaders.add(POWERED_BY);
- if (getHttpConfiguration().getSendDateHeader())
+ if (httpConfiguration.getSendDateHeader())
responseHeaders.add(getConnectionMetaData().getConnector().getServer().getDateField());
+ long idleTO = httpConfiguration.getIdleTimeout();
+ _oldIdleTimeout = _stream.getIdleTimeout();
+ if (idleTO >= 0 && _oldIdleTimeout != idleTO)
+ _stream.setIdleTimeout(idleTO);
+
+
// This is deliberately not serialized to allow a handler to block.
return _handlerInvoker;
}
@@ -326,15 +346,43 @@ public Invocable.InvocationType getInvocationType()
return Invocable.getInvocationType(onContent);
}
- public Runnable onFailure(Throwable x)
+ @Override
+ public Runnable onIdleTimeout(TimeoutException t)
{
- if (LOG.isDebugEnabled())
- LOG.debug("onFailure {}", this, x);
+ Predicate onIdleTimeout;
+ try (AutoLock ignored = _lock.lock())
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("onIdleTimeout {}", this, t);
+ onIdleTimeout = _onIdleTimeout;
+ }
+
+ if (onIdleTimeout != null)
+ {
+ Runnable onIdle = () ->
+ {
+ if (onIdleTimeout.test(t))
+ {
+ Runnable task = onFailure(t);
+ if (task != null)
+ task.run();
+ }
+ };
+ return _serializedInvoker.offer(onIdle);
+ }
+ return onFailure(t); // TODO can we avoid double lock?
+ }
+ @Override
+ public Runnable onFailure(Throwable x)
+ {
HttpStream stream;
Runnable task;
try (AutoLock ignored = _lock.lock())
{
+ if (LOG.isDebugEnabled())
+ LOG.debug("onFailure {}", this, x);
+
// If the channel doesn't have a stream, then the error is ignored.
if (_stream == null)
return null;
@@ -349,14 +397,13 @@ public Runnable onFailure(Throwable x)
}
// Set the error to arrange for any subsequent reads, demands or writes to fail.
- if (_error == null)
+ if (_failure == null)
{
- _error = Content.Chunk.from(x);
+ _failure = Content.Chunk.from(x);
}
- else if (_error.getCause() != x)
+ else if (ExceptionUtil.areNotAssociated(_failure.getCause(), x) && _failure.getCause().getClass() != x.getClass())
{
- _error.getCause().addSuppressed(x);
- return null;
+ _failure.getCause().addSuppressed(x);
}
// If not handled, then we just fail the request callback
@@ -377,18 +424,17 @@ else if (_error.getCause() != x)
ChannelRequest request = _request;
Runnable invokeListeners = () ->
{
- Predicate onError;
+ Consumer onFailure;
try (AutoLock ignore = _lock.lock())
{
- onError = _onError;
+ onFailure = _onFailure;
}
try
{
if (LOG.isDebugEnabled())
- LOG.debug("invokeListeners {} {}", HttpChannelState.this, onError, x);
- if (onError.test(x))
- return;
+ LOG.debug("invokeListeners {} {}", HttpChannelState.this, onFailure, x);
+ onFailure.accept(x);
}
catch (Throwable throwable)
{
@@ -586,7 +632,7 @@ public void run()
stream = _stream;
_handling = null;
_handled = true;
- failure = _failure;
+ failure = _callbackFailure;
callbackCompleted = _callbackCompleted;
lastStreamSendComplete = lockedIsLastStreamSendCompleted();
completeStream = callbackCompleted && lastStreamSendComplete;
@@ -640,7 +686,7 @@ public void failed(Throwable failure)
_streamSendState = StreamSendState.LAST_COMPLETE;
completeStream = _handling == null;
stream = _stream;
- failure = _failure = ExceptionUtil.combine(_failure, failure);
+ failure = _callbackFailure = ExceptionUtil.combine(_callbackFailure, failure);
}
if (completeStream)
completeStream(stream, failure);
@@ -663,6 +709,10 @@ private void completeStream(HttpStream stream, Throwable failure)
Parts parts = (Parts)_request.getAttribute(Parts.class.getName());
if (parts != null)
parts.close();
+
+ long idleTO = getHttpConfiguration().getIdleTimeout();
+ if (idleTO > 0 && _oldIdleTimeout != idleTO)
+ stream.setIdleTimeout(_oldIdleTimeout);
}
finally
{
@@ -867,7 +917,7 @@ public Content.Chunk read()
{
HttpChannelState httpChannel = lockedGetHttpChannelState();
- Content.Chunk error = httpChannel._error;
+ Content.Chunk error = httpChannel._failure;
if (error != null)
return error;
@@ -913,7 +963,7 @@ public void demand(Runnable demandCallback)
if (LOG.isDebugEnabled())
LOG.debug("demand {}", httpChannel);
- error = httpChannel._error != null;
+ error = httpChannel._failure != null;
if (!error)
{
if (httpChannel._onContentAvailable != null)
@@ -944,30 +994,66 @@ public void push(MetaData.Request resource)
}
@Override
- public boolean addErrorListener(Predicate onError)
+ public void addIdleTimeoutListener(Predicate onIdleTimeout)
{
try (AutoLock ignored = _lock.lock())
{
HttpChannelState httpChannel = lockedGetHttpChannelState();
- if (httpChannel._error != null)
- return false;
+ if (httpChannel._failure != null)
+ return;
- if (httpChannel._onError == null)
+ if (httpChannel._onIdleTimeout == null)
{
- httpChannel._onError = onError;
+ httpChannel._onIdleTimeout = onIdleTimeout;
}
else
{
- Predicate previous = httpChannel._onError;
- httpChannel._onError = throwable ->
+ Predicate previous = httpChannel._onIdleTimeout;
+ httpChannel._onIdleTimeout = throwable ->
{
if (!previous.test(throwable))
- return onError.test(throwable);
+ return onIdleTimeout.test(throwable);
return true;
};
}
- return true;
+ }
+ }
+
+ @Override
+ public void addFailureListener(Consumer onFailure)
+ {
+ try (AutoLock ignored = _lock.lock())
+ {
+ HttpChannelState httpChannel = lockedGetHttpChannelState();
+
+ if (httpChannel._failure != null)
+ return;
+
+ if (httpChannel._onFailure == null)
+ {
+ httpChannel._onFailure = onFailure;
+ }
+ else
+ {
+ Consumer previous = httpChannel._onFailure;
+ httpChannel._onFailure = throwable ->
+ {
+ try
+ {
+ previous.accept(throwable);
+ }
+ catch (Throwable t)
+ {
+ if (ExceptionUtil.areNotAssociated(throwable, t))
+ throwable.addSuppressed(t);
+ }
+ finally
+ {
+ onFailure.accept(throwable);
+ }
+ };
+ }
}
}
@@ -1005,33 +1091,26 @@ public String toString()
public static class ChannelResponse implements Response, Callback
{
private final ChannelRequest _request;
- private int _status;
+ private final ResponseHttpFields _httpFields;
+ protected int _status;
private long _contentBytesWritten;
private Supplier _trailers;
private Callback _writeCallback;
- protected boolean _errorMode;
private ChannelResponse(ChannelRequest request)
{
_request = request;
+ _httpFields = getResponseHttpFields(_request.lockedGetHttpChannelState());
}
- private void lockedPrepareErrorResponse()
+ protected ResponseHttpFields getResponseHttpFields(HttpChannelState httpChannelState)
{
- // reset the response state, so we can generate an error response,
- // remembering any server or date headers (probably a nicer way of doing this).
- HttpChannelState httpChannelState = _request.lockedGetHttpChannelState();
- HttpField serverField = httpChannelState._responseHeaders.getField(HttpHeader.SERVER);
- HttpField dateField = httpChannelState._responseHeaders.getField(HttpHeader.DATE);
- httpChannelState._responseHeaders.reset();
- httpChannelState._committedContentLength = -1;
- reset();
- if (serverField != null)
- httpChannelState._responseHeaders.put(serverField);
- if (dateField != null)
- httpChannelState._responseHeaders.put(dateField);
- setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
- _errorMode = true;
+ return httpChannelState._responseHeaders;
+ }
+
+ protected ResponseHttpFields getResponseHttpFields()
+ {
+ return _httpFields;
}
private boolean lockedIsWriting()
@@ -1075,7 +1154,7 @@ public void setStatus(int code)
@Override
public HttpFields.Mutable getHeaders()
{
- return _request.getHttpChannelState()._responseHeaders;
+ return _httpFields;
}
@Override
@@ -1115,19 +1194,21 @@ public void write(boolean last, ByteBuffer content, Callback callback)
if (_writeCallback != null)
failure = new IllegalStateException("write pending");
- else if (!_errorMode && httpChannelState._error != null)
- failure = httpChannelState._error.getCause();
- else if (contentLength >= 0)
+ else
{
- // If the content length were not compatible with what was written, then we need to abort.
- String lengthError = (totalWritten > contentLength) ? "written %d > %d content-length"
- : (last && totalWritten < contentLength) ? "written %d < %d content-length" : null;
- if (lengthError != null)
+ failure = getFailure(httpChannelState);
+ if (failure == null && contentLength >= 0)
{
- String message = lengthError.formatted(totalWritten, contentLength);
- if (LOG.isDebugEnabled())
- LOG.debug("fail {} {}", callback, message);
- failure = new IOException(message);
+ // If the content length were not compatible with what was written, then we need to abort.
+ String lengthError = (totalWritten > contentLength) ? "written %d > %d content-length"
+ : (last && totalWritten < contentLength) ? "written %d < %d content-length" : null;
+ if (lengthError != null)
+ {
+ String message = lengthError.formatted(totalWritten, contentLength);
+ if (LOG.isDebugEnabled())
+ LOG.debug("fail {} {}", callback, message);
+ failure = new IOException(message);
+ }
}
}
@@ -1151,7 +1232,7 @@ else if (failure != null)
_writeCallback = callback;
_contentBytesWritten = totalWritten;
stream = httpChannelState._stream;
- if (httpChannelState._responseHeaders.commit())
+ if (_httpFields.commit())
responseMetaData = lockedPrepareResponse(httpChannelState, last);
}
}
@@ -1164,6 +1245,12 @@ else if (failure != null)
}
}
+ protected Throwable getFailure(HttpChannelState httpChannelState)
+ {
+ Content.Chunk.Error failure = httpChannelState._failure;
+ return failure == null ? null : failure.getCause();
+ }
+
/**
* Called when the call to
* {@link HttpStream#send(MetaData.Request, MetaData.Response, boolean, ByteBuffer, Callback)}
@@ -1227,7 +1314,7 @@ public InvocationType getInvocationType()
@Override
public boolean isCommitted()
{
- return _request.getHttpChannelState()._responseHeaders.isCommitted();
+ return _httpFields.isCommitted();
}
@Override
@@ -1238,7 +1325,7 @@ public boolean isCompletedSuccessfully()
if (_request._httpChannelState == null)
return false;
- return _request._httpChannelState._callbackCompleted && _request._httpChannelState._failure == null;
+ return _request._httpChannelState._callbackCompleted && _request._httpChannelState._callbackFailure == null;
}
}
@@ -1276,7 +1363,7 @@ MetaData.Response lockedPrepareResponse(HttpChannelState httpChannel, boolean la
_status = HttpStatus.OK_200;
// Can we set the content length?
- HttpFields.Mutable mutableHeaders = httpChannel._responseHeaders.getMutableHttpFields();
+ HttpFields.Mutable mutableHeaders = _httpFields.getMutableHttpFields();
httpChannel._committedContentLength = mutableHeaders.getLongField(HttpHeader.CONTENT_LENGTH);
if (last && httpChannel._committedContentLength < 0L)
{
@@ -1288,7 +1375,7 @@ MetaData.Response lockedPrepareResponse(HttpChannelState httpChannel, boolean la
return new MetaData.Response(
_status, null, httpChannel.getConnectionMetaData().getHttpVersion(),
- httpChannel._responseHeaders,
+ _httpFields,
httpChannel._committedContentLength,
getTrailersSupplier()
);
@@ -1326,6 +1413,8 @@ public void succeeded()
ChannelResponse response;
MetaData.Response responseMetaData = null;
boolean completeStream;
+ ErrorResponse errorResponse = null;
+
try (AutoLock ignored = _request._lock.lock())
{
request = _request;
@@ -1343,7 +1432,7 @@ public void succeeded()
if (lockedCompleteCallback())
return;
- assert httpChannelState._failure == null;
+ assert httpChannelState._callbackFailure == null;
needLastStreamSend = httpChannelState.lockedLastStreamSend();
completeStream = !needLastStreamSend && httpChannelState._handling == null && httpChannelState.lockedIsLastStreamSendCompleted();
@@ -1367,9 +1456,9 @@ public void succeeded()
if (failure != null)
{
- httpChannelState._failure = failure;
+ httpChannelState._callbackFailure = failure;
if (!stream.isCommitted())
- response.lockedPrepareErrorResponse();
+ errorResponse = new ErrorResponse(request);
else
completeStream = true;
}
@@ -1378,8 +1467,8 @@ public void succeeded()
if (LOG.isDebugEnabled())
LOG.debug("succeeded: failure={} needLastStreamSend={} {}", failure, needLastStreamSend, this);
- if (failure != null)
- Response.writeError(request, response, new ErrorCallback(request, stream, failure), failure);
+ if (errorResponse != null)
+ Response.writeError(request, errorResponse, new ErrorCallback(request, errorResponse, stream, failure), failure);
else if (needLastStreamSend)
stream.send(_request._metaData, responseMetaData, true, null, httpChannelState._handlerInvoker);
else if (completeStream)
@@ -1398,21 +1487,19 @@ public void failed(Throwable failure)
// Called when the request/response cycle is completing with a failure.
HttpStream stream;
ChannelRequest request;
- ChannelResponse response;
HttpChannelState httpChannelState;
- boolean writeError;
+ ErrorResponse errorResponse = null;
try (AutoLock ignored = _request._lock.lock())
{
httpChannelState = _request._httpChannelState;
stream = httpChannelState._stream;
request = _request;
- response = httpChannelState._response;
if (lockedCompleteCallback())
return;
- assert httpChannelState._failure == null;
+ assert httpChannelState._callbackFailure == null;
- httpChannelState._failure = failure;
+ httpChannelState._callbackFailure = failure;
// Consume any input.
Throwable unconsumed = stream.consumeAvailable();
@@ -1422,13 +1509,12 @@ public void failed(Throwable failure)
if (LOG.isDebugEnabled())
LOG.debug("failed stream.isCommitted={}, response.isCommitted={} {}", httpChannelState._stream.isCommitted(), httpChannelState._response.isCommitted(), this);
- writeError = !stream.isCommitted();
- if (writeError)
- response.lockedPrepareErrorResponse();
+ if (!stream.isCommitted())
+ errorResponse = new ErrorResponse(request);
}
- if (writeError)
- Response.writeError(request, response, new ErrorCallback(request, stream, failure), failure);
+ if (errorResponse != null)
+ Response.writeError(request, errorResponse, new ErrorCallback(request, errorResponse, stream, failure), failure);
else
_request.getHttpChannelState()._handlerInvoker.failed(failure);
}
@@ -1471,6 +1557,53 @@ public InvocationType getInvocationType()
}
}
+ /**
+ * Used as the {@link Response} when writing the error response
+ * from {@link HttpChannelState.ChannelCallback#failed(Throwable)}.
+ */
+ private static class ErrorResponse extends ChannelResponse
+ {
+ public ErrorResponse(ChannelRequest request)
+ {
+ super(request);
+ _status = HttpStatus.INTERNAL_SERVER_ERROR_500;
+ }
+
+ @Override
+ protected Throwable getFailure(HttpChannelState httpChannelState)
+ {
+ // we ignore channel failures so we can try to generate an error response.
+ return null;
+ }
+
+ @Override
+ protected ResponseHttpFields getResponseHttpFields(HttpChannelState httpChannelState)
+ {
+ httpChannelState._committedContentLength = -1;
+ HttpFields original = super.getResponseHttpFields(httpChannelState);
+ ResponseHttpFields httpFields = new ResponseHttpFields();
+
+ for (HttpField field : original)
+ {
+ HttpHeader header = field.getHeader();
+ if (header == HttpHeader.SERVER || header == HttpHeader.DATE)
+ httpFields.add(field);
+ }
+ return httpFields;
+ }
+
+ @Override
+ MetaData.Response lockedPrepareResponse(HttpChannelState httpChannelState, boolean last)
+ {
+ MetaData.Response httpFields = super.lockedPrepareResponse(httpChannelState, last);
+ httpChannelState._response._status = _status;
+ HttpFields.Mutable originalResponseFields = httpChannelState._responseHeaders.getMutableHttpFields();
+ originalResponseFields.clear();
+ originalResponseFields.add(getResponseHttpFields());
+ return httpFields;
+ }
+ }
+
/**
* Used as the {@link Response} and {@link Callback} when writing the error response
* from {@link HttpChannelState.ChannelCallback#failed(Throwable)}.
@@ -1478,12 +1611,14 @@ public InvocationType getInvocationType()
private static class ErrorCallback implements Callback
{
private final ChannelRequest _request;
+ private final ErrorResponse _errorResponse;
private final HttpStream _stream;
private final Throwable _failure;
- public ErrorCallback(ChannelRequest request, HttpStream stream, Throwable failure)
+ public ErrorCallback(ChannelRequest request, ErrorResponse response, HttpStream stream, Throwable failure)
{
_request = request;
+ _errorResponse = response;
_stream = stream;
_failure = failure;
}
@@ -1507,8 +1642,8 @@ public void succeeded()
// Did the ErrorHandler do the last write?
needLastWrite = httpChannelState.lockedLastStreamSend();
- if (needLastWrite && httpChannelState._responseHeaders.commit())
- responseMetaData = httpChannelState._response.lockedPrepareResponse(httpChannelState, true);
+ if (needLastWrite && _errorResponse.getResponseHttpFields().commit())
+ responseMetaData = _errorResponse.lockedPrepareResponse(httpChannelState, true);
}
if (needLastWrite)
@@ -1538,13 +1673,16 @@ public void failed(Throwable x)
if (LOG.isDebugEnabled())
LOG.debug("ErrorWrite failed: {}", this, x);
Throwable failure;
+ HttpChannelState httpChannelState;
try (AutoLock ignored = _request._lock.lock())
{
failure = _failure;
+ httpChannelState = _request.lockedGetHttpChannelState();
+ httpChannelState._response._status = _errorResponse._status;
}
if (ExceptionUtil.areNotAssociated(failure, x))
failure.addSuppressed(x);
- _request.getHttpChannelState()._handlerInvoker.failed(failure);
+ httpChannelState._handlerInvoker.failed(failure);
}
@Override
@@ -1566,7 +1704,7 @@ protected void onError(Runnable task, Throwable failure)
{
callbackCompleted = _callbackCompleted;
request = _request;
- error = _request == null ? null : _error;
+ error = _request == null ? null : _failure;
}
if (request == null || callbackCompleted)
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java
index 56fb22d86fa4..a566d1610911 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java
@@ -22,6 +22,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@@ -657,6 +658,17 @@ protected void onFillInterestedFailed(Throwable cause)
super.onFillInterestedFailed(cause);
}
+ @Override
+ public boolean onIdleExpired(TimeoutException timeout)
+ {
+ if (_httpChannel.getRequest() == null)
+ return true;
+ Runnable task = _httpChannel.onIdleTimeout(timeout);
+ if (task != null)
+ getExecutor().execute(task);
+ return false; // We've handle the exception
+ }
+
@Override
public void onOpen()
{
@@ -1448,6 +1460,18 @@ else if (_expects100Continue)
_sendCallback.iterate();
}
+ @Override
+ public long getIdleTimeout()
+ {
+ return getEndPoint().getIdleTimeout();
+ }
+
+ @Override
+ public void setIdleTimeout(long idleTimeoutMs)
+ {
+ getEndPoint().setIdleTimeout(idleTimeoutMs);
+ }
+
@Override
public boolean isCommitted()
{
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
index 82f3e3e396e3..0758b29508cd 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
@@ -583,15 +583,25 @@ protected static class WaitHandler extends Handler.Abstract
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
{
+ request.addIdleTimeoutListener(t -> false);
response.setStatus(200);
try
{
- Thread.sleep(2000);
+ Thread.sleep(MAX_IDLE_TIME * 3 / 2);
}
catch (Exception e)
{
e.printStackTrace();
}
+
+ // TODO what do we do about the failing write?
+ // should timeout errors be non persistent?
+ Callback ocb = callback;
+ callback = Callback.from(ocb::succeeded, t ->
+ {
+ t.printStackTrace();
+ ocb.failed(t);
+ });
Content.Sink.write(response, true, "Hello World\r\n", callback);
return true;
}
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpChannelTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpChannelTest.java
index 19df0a1fee30..518428b9dcd2 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpChannelTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpChannelTest.java
@@ -1175,13 +1175,9 @@ public void testOnError() throws Exception
public boolean handle(Request request, Response response, Callback callback)
{
handling.set(response);
- request.addErrorListener(t -> false);
- request.addErrorListener(t -> !error.compareAndSet(null, t));
- request.addErrorListener(t ->
- {
- callback.failed(t);
- return true;
- });
+ request.addFailureListener(t -> error.set(null));
+ request.addFailureListener(t -> error.compareAndSet(null, t));
+ request.addFailureListener(t -> error.compareAndSet(null, new Throwable("WRONG")));
return true;
}
};
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
index 6944d49026bc..5e7b46a85c87 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpConnectionTest.java
@@ -44,7 +44,6 @@
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.handler.DumpHandler;
-import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.server.internal.HttpConnection;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
@@ -1158,24 +1157,13 @@ public boolean handle(Request request, Response response, Callback callback)
});
_server.start();
- String response = null;
- try (StacklessLogging stackless = new StacklessLogging(HttpChannelState.class))
- {
- LOG.info("Expect IOException: Response header too large...");
- response = _connector.getResponse("GET / HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "\r\n"
- );
+ String response = _connector.getResponse("GET / HTTP/1.1\r\n" +
+ "Host: localhost\r\n" +
+ "\r\n"
+ );
- checkContains(response, 0, "HTTP/1.1 500");
- assertTrue(checkError.await(1, TimeUnit.SECONDS));
- }
- catch (Exception e)
- {
- if (response != null)
- System.err.println(response);
- throw e;
- }
+ checkContains(response, 0, "HTTP/1.1 500");
+ assertTrue(checkError.await(1, TimeUnit.SECONDS));
}
@Test
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/MockHttpStream.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/MockHttpStream.java
index bf2c785f7384..cb91cf235823 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/MockHttpStream.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/MockHttpStream.java
@@ -218,6 +218,17 @@ public void send(MetaData.Request request, MetaData.Response response, boolean l
callback.succeeded();
}
+ @Override
+ public long getIdleTimeout()
+ {
+ return 0;
+ }
+
+ @Override
+ public void setIdleTimeout(long idleTimeoutMs)
+ {
+ }
+
@Override
public boolean isCommitted()
{
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java
index 5702f13765c2..b2cf089d6d23 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ServerTest.java
@@ -13,8 +13,13 @@
package org.eclipse.jetty.server;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
@@ -26,6 +31,7 @@
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.QuietException;
+import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.internal.HttpChannelState;
import org.eclipse.jetty.server.internal.HttpConnection;
import org.eclipse.jetty.util.Blocker;
@@ -41,12 +47,17 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.jupiter.api.Assertions.assertTrue;
public class ServerTest
{
+ private static final long IDLE_TIMEOUT = 1000L;
private Server _server;
+ private ContextHandler _context;
private LocalConnector _connector;
private final AtomicReference _afterHandle = new AtomicReference<>();
@@ -54,6 +65,8 @@ public class ServerTest
public void prepare() throws Exception
{
_server = new Server();
+ _context = new ContextHandler("/");
+ _server.setHandler(_context);
_connector = new LocalConnector(_server, new HttpConnectionFactory()
{
@Override
@@ -95,7 +108,8 @@ public Runnable onRequest(MetaData.Request request)
return configure(connection, connector, endPoint);
}
});
- _connector.setIdleTimeout(60000);
+ _connector.setIdleTimeout(IDLE_TIMEOUT);
+ _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setIdleTimeout(IDLE_TIMEOUT);
_server.addConnector(_connector);
}
@@ -109,10 +123,10 @@ public void dispose() throws Exception
@Test
public void testSimpleGET() throws Exception
{
- _server.setHandler(new Handler.Abstract()
+ _context.setHandler(new Handler.Abstract()
{
@Override
- public boolean handle(Request request, Response response, Callback callback) throws Exception
+ public boolean handle(Request request, Response response, Callback callback)
{
response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain");
Content.Sink.write(response, true, "Hello", callback);
@@ -154,7 +168,7 @@ public static Stream completionScenarios()
@MethodSource("completionScenarios")
public void testCompletion(boolean succeeded, boolean handling, boolean written, boolean last) throws Exception
{
- _server.setHandler(new Handler.Abstract(Invocable.InvocationType.BLOCKING)
+ _context.setHandler(new Handler.Abstract(Invocable.InvocationType.BLOCKING)
{
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception
@@ -186,8 +200,6 @@ public boolean handle(Request request, Response response, Callback callback) thr
\r
""";
String rawResponse = _connector.getResponse(request);
- // System.err.printf("succeeded=%b handling=%b written=%b last=%b%n", succeeded, handling, written, last);
- // System.err.println(rawResponse);
if (succeeded || written)
assertThat(rawResponse, containsString("HTTP/1.1 200 OK"));
@@ -212,4 +224,187 @@ public boolean handle(Request request, Response response, Callback callback) thr
assertThat(rawResponse, containsString("Content-Length:"));
}
}
+
+ @Test
+ public void testIdleTimeoutNoListener() throws Exception
+ {
+ // See ServerTimeoutsTest for more complete idle timeout testing.
+ _context.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ // Handler never completes the callback
+ return true;
+ }
+ });
+ _server.start();
+
+ String request = """
+ GET /path HTTP/1.0\r
+ Host: hostname\r
+ \r
+ """;
+ String rawResponse = _connector.getResponse(request);
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+ assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
+ assertThat(response.getContent(), containsString("HTTP ERROR 500 java.util.concurrent.TimeoutException: Idle timeout expired:"));
+ }
+
+ @Test
+ public void testIdleTimeoutNoListenerHttpConfigurationOnly() throws Exception
+ {
+ // See ServerTimeoutsTest for more complete idle timeout testing.
+
+ _context.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ // Handler never completes the callback
+ return true;
+ }
+ });
+
+ _connector.setIdleTimeout(10 * IDLE_TIMEOUT);
+ _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setIdleTimeout(IDLE_TIMEOUT);
+
+ _server.start();
+
+ String request = """
+ GET /path HTTP/1.0\r
+ Host: hostname\r
+ \r
+ """;
+ String rawResponse = _connector.getResponse(request);
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+ assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
+ assertThat(response.getContent(), containsString("HTTP ERROR 500 java.util.concurrent.TimeoutException: Idle timeout expired:"));
+ }
+
+ @Test
+ public void testIdleTimeoutFalseListener() throws Exception
+ {
+ // See ServerTimeoutsTest for more complete idle timeout testing.
+ CompletableFuture callbackOnTimeout = new CompletableFuture<>();
+ _context.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ request.addIdleTimeoutListener(t -> !callbackOnTimeout.complete(callback));
+ return true;
+ }
+ });
+ _server.start();
+
+ String request = """
+ GET /path HTTP/1.0\r
+ Host: hostname\r
+ \r
+ """;
+
+ try (LocalConnector.LocalEndPoint localEndPoint = _connector.executeRequest(request))
+ {
+ callbackOnTimeout.get(3 * IDLE_TIMEOUT, TimeUnit.MILLISECONDS).succeeded();
+ String rawResponse = localEndPoint.getResponse();
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ }
+ }
+
+ @Test
+ public void testIdleTimeoutWriteCallback() throws Exception
+ {
+ CompletableFuture onTimeout = new CompletableFuture<>();
+ CompletableFuture writeFail = new CompletableFuture<>();
+ _context.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ Runnable write = new Runnable()
+ {
+ final ByteBuffer buffer = ByteBuffer.allocate(128 * 1024 * 1024);
+ @Override
+ public void run()
+ {
+ response.write(false, buffer, Callback.from(this,
+ t ->
+ {
+ writeFail.complete(t);
+ callback.failed(t);
+ }));
+ }
+ };
+
+ request.addIdleTimeoutListener(t ->
+ {
+ request.getComponents().getThreadPool().execute(write);
+ return onTimeout.complete(t);
+ });
+
+ return true;
+ }
+ });
+ _server.start();
+
+ String request = """
+ GET /path HTTP/1.0\r
+ Host: localhost\r
+ \r
+ """;
+ try (LocalConnector.LocalEndPoint ignored = _connector.executeRequest(request))
+ {
+ Throwable x = onTimeout.get(2 * IDLE_TIMEOUT, TimeUnit.MILLISECONDS);
+ assertThat(x, instanceOf(TimeoutException.class));
+ x = writeFail.get(IDLE_TIMEOUT / 2, TimeUnit.MILLISECONDS);
+ assertThat(x, instanceOf(TimeoutException.class));
+ }
+ }
+
+ @Test
+ public void testListenersInContext() throws Exception
+ {
+ CountDownLatch latch = new CountDownLatch(3);
+ _context.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ assertThat(ContextHandler.getCurrentContext(), sameInstance(_context.getContext()));
+ latch.countDown();
+
+ request.addIdleTimeoutListener(t ->
+ {
+ assertThat(ContextHandler.getCurrentContext(), sameInstance(_context.getContext()));
+ latch.countDown();
+ return true;
+ });
+
+ request.addFailureListener(t ->
+ {
+ assertThat(ContextHandler.getCurrentContext(), sameInstance(_context.getContext()));
+ latch.countDown();
+ });
+ return true;
+ }
+ });
+ _server.start();
+
+ String request = """
+ GET /path HTTP/1.0\r
+ Host: hostname\r
+ \r
+ """;
+
+ try (LocalConnector.LocalEndPoint localEndPoint = _connector.executeRequest(request))
+ {
+ assertTrue(latch.await(3 * IDLE_TIMEOUT, TimeUnit.MILLISECONDS));
+ String rawResponse = localEndPoint.getResponse();
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+ assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
+ }
+ }
+
}
diff --git a/jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/TestableRequest.java b/jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/TestableRequest.java
index 0053692953d0..3bda4f7edad1 100644
--- a/jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/TestableRequest.java
+++ b/jetty-core/jetty-session/src/test/java/org/eclipse/jetty/session/TestableRequest.java
@@ -15,6 +15,8 @@
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -161,9 +163,13 @@ public void fail(Throwable failure)
}
@Override
- public boolean addErrorListener(Predicate onError)
+ public void addIdleTimeoutListener(Predicate onIdleTimeout)
+ {
+ }
+
+ @Override
+ public void addFailureListener(Consumer onFailure)
{
- return false;
}
@Override
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/pom.xml b/jetty-core/jetty-tests/jetty-test-client-transports/pom.xml
index 8cf90ab9fc26..46b1f6868cee 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/pom.xml
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/pom.xml
@@ -62,6 +62,11 @@
jetty-http3-client-transport
test
+
+ org.eclipse.jetty
+ jetty-slf4j-impl
+ test
+
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java
index 5ce310c2c9f8..7f4130371bf3 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/HttpClientTimeoutTest.java
@@ -246,7 +246,7 @@ protected SslConnection newSslConnection(ByteBufferPool bufferPool, Executor exe
return new SslConnection(bufferPool, executor, endPoint, engine)
{
@Override
- protected boolean onReadTimeout(Throwable timeout)
+ protected boolean onReadTimeout(TimeoutException timeout)
{
sslIdle.set(true);
return super.onReadTimeout(timeout);
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/ServerTimeoutsTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/ServerTimeoutsTest.java
index c0d2873f48f0..7cd669cb9fb9 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/ServerTimeoutsTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/ServerTimeoutsTest.java
@@ -13,8 +13,200 @@
package org.eclipse.jetty.test.client.transport;
-// TODO: similar to eeX ServerTimeoutsTest but with Handler semantic.
-// For example, we may decide to not ignore the timeouts if there is a thread dispatched to the Handler.
-public class ServerTimeoutsTest
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
+
+import org.eclipse.jetty.client.AsyncRequestContent;
+import org.eclipse.jetty.client.ContentResponse;
+import org.eclipse.jetty.client.FutureResponseListener;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.Content;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
+import org.eclipse.jetty.util.Callback;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsStringIgnoringCase;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ServerTimeoutsTest extends AbstractTest
{
+ private static final long IDLE_TIMEOUT = 1000L;
+
+ @Override
+ protected void prepareServer(Transport transport, Handler handler) throws Exception
+ {
+ super.prepareServer(transport, handler);
+ setStreamIdleTimeout(IDLE_TIMEOUT);
+ }
+
+ public static Stream transportsAndTrueIdleTimeoutListeners()
+ {
+ Collection transports = transports();
+ return Stream.concat(
+ transports.stream().map(t -> Arguments.of(t, false)),
+ transports.stream().map(t -> Arguments.arguments(t, true)));
+ }
+
+ @ParameterizedTest
+ @MethodSource("transportsAndTrueIdleTimeoutListeners")
+ public void testIdleTimeout(Transport transport, boolean listener) throws Exception
+ {
+ AtomicBoolean listenerCalled = new AtomicBoolean();
+ start(transport, new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+
+ if (listener)
+ request.addIdleTimeoutListener(t -> listenerCalled.compareAndSet(false, true));
+
+ // Do not complete the callback, so it idle times out.
+ return true;
+ }
+ });
+
+ ContentResponse response = client.newRequest(newURI(transport))
+ .timeout(5 * IDLE_TIMEOUT, TimeUnit.MILLISECONDS)
+ .send();
+
+ assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
+ assertThat(response.getContentAsString(), containsStringIgnoringCase("HTTP ERROR 500 java.util.concurrent.TimeoutException: Idle timeout"));
+ if (listener)
+ assertTrue(listenerCalled.get());
+ }
+
+ @ParameterizedTest
+ @MethodSource("transportsAndTrueIdleTimeoutListeners")
+ public void testIdleTimeoutWithDemand(Transport transport, boolean listener) throws Exception
+ {
+ AtomicBoolean listenerCalled = new AtomicBoolean();
+ CountDownLatch demanded = new CountDownLatch(1);
+ AtomicReference requestRef = new AtomicReference<>();
+ AtomicReference callbackRef = new AtomicReference<>();
+ start(transport, new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+
+ if (listener)
+ request.addIdleTimeoutListener(t -> listenerCalled.compareAndSet(false, true));
+ requestRef.set(request);
+ callbackRef.set(callback);
+ request.demand(demanded::countDown);
+ return true;
+ }
+ });
+
+ // The response will not be completed, so use a specialized listener.
+ AsyncRequestContent content = new AsyncRequestContent();
+ org.eclipse.jetty.client.Request request = client.newRequest(newURI(transport))
+ .timeout(IDLE_TIMEOUT * 5, TimeUnit.MILLISECONDS)
+ .headers(f -> f.put(HttpHeader.CONTENT_LENGTH, 10))
+ .onResponseSuccess(s ->
+ content.close())
+ .body(content);
+ FutureResponseListener futureResponse = new FutureResponseListener(request);
+ request.send(futureResponse);
+
+ // Demand is invoked by the idle timeout
+ assertTrue(demanded.await(2 * IDLE_TIMEOUT, TimeUnit.MILLISECONDS));
+
+ // Reads should yield the idle timeout.
+ Content.Chunk chunk = requestRef.get().read();
+ assertThat(chunk, instanceOf(Content.Chunk.Error.class));
+ Throwable cause = ((Content.Chunk.Error)chunk).getCause();
+ assertThat(cause, instanceOf(TimeoutException.class));
+
+ // Complete the callback as the error listener promised.
+ callbackRef.get().failed(cause);
+
+ ContentResponse response = futureResponse.get(IDLE_TIMEOUT / 2, TimeUnit.MILLISECONDS);
+ assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
+ assertThat(response.getContentAsString(), containsStringIgnoringCase("HTTP ERROR 500 java.util.concurrent.TimeoutException: Idle timeout"));
+ if (listener)
+ assertTrue(listenerCalled.get());
+ }
+
+ @ParameterizedTest
+ @MethodSource("transports")
+ public void testIdleTimeoutErrorListenerReturnsFalse(Transport transport) throws Exception
+ {
+ AtomicReference responseRef = new AtomicReference<>();
+ CompletableFuture callbackOnTimeout = new CompletableFuture<>();
+ start(transport, new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ responseRef.set(response);
+ request.addIdleTimeoutListener(t ->
+ {
+ callbackOnTimeout.complete(callback);
+ return false; // ignore timeout
+ });
+ return true;
+ }
+ });
+
+ org.eclipse.jetty.client.Request request = client.newRequest(newURI(transport))
+ .timeout(IDLE_TIMEOUT * 5, TimeUnit.MILLISECONDS);
+ FutureResponseListener futureResponse = new FutureResponseListener(request);
+ request.send(futureResponse);
+
+ // Get the callback as promised by the error listener.
+ Callback callback = callbackOnTimeout.get(3 * IDLE_TIMEOUT, TimeUnit.MILLISECONDS);
+ assertNotNull(callback);
+ Content.Sink.write(responseRef.get(), true, "OK", callback);
+
+ ContentResponse response = futureResponse.get(IDLE_TIMEOUT / 2, TimeUnit.MILLISECONDS);
+ assertThat(response.getStatus(), is(HttpStatus.OK_200));
+ assertThat(response.getContentAsString(), is("OK"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("transportsNoFCGI")
+ public void testIdleTimeoutErrorListenerReturnsFalseThenTrue(Transport transport) throws Exception
+ {
+ // TODO fix FCGI for multiple timeouts
+ AtomicReference error = new AtomicReference<>();
+ start(transport, new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ request.addIdleTimeoutListener(t -> error.getAndSet(t) != null);
+ return true;
+ }
+ });
+
+ ContentResponse response = client.newRequest(newURI(transport))
+ .timeout(IDLE_TIMEOUT * 5, TimeUnit.MILLISECONDS)
+ .send();
+
+ // The first time the listener returns true, but does not complete the callback,
+ // so another idle timeout elapses.
+ // The second time the listener returns false and the implementation produces the response.
+ assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
+ assertThat(response.getContentAsString(), containsStringIgnoringCase("HTTP ERROR 500 java.util.concurrent.TimeoutException: Idle timeout"));
+ assertThat(error.get(), instanceOf(TimeoutException.class));
+ }
+
+ // TODO write side tests
}
diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketConnection.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketConnection.java
index 462c68b7ddcf..66d985299278 100644
--- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketConnection.java
+++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/WebSocketConnection.java
@@ -21,6 +21,7 @@
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.LongAdder;
import org.eclipse.jetty.io.AbstractConnection;
@@ -218,13 +219,13 @@ public void onClose(Throwable cause)
}
@Override
- public boolean onIdleExpired()
+ public boolean onIdleExpired(TimeoutException timeoutException)
{
if (LOG.isDebugEnabled())
LOG.debug("onIdleExpired()");
// treat as a handler error because socket is still open
- coreSession.processHandlerError(new WebSocketTimeoutException("Connection Idle Timeout"), Callback.NOOP);
+ coreSession.processHandlerError(new WebSocketTimeoutException("Connection Idle Timeout", timeoutException), Callback.NOOP);
return true;
}
@@ -234,7 +235,7 @@ public boolean onIdleExpired()
* @return true to signal that the endpoint must be closed, false to keep the endpoint open
*/
@Override
- protected boolean onReadTimeout(Throwable timeout)
+ protected boolean onReadTimeout(TimeoutException timeout)
{
if (LOG.isDebugEnabled())
LOG.debug("onReadTimeout()");
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java
index a6f0f6c0816f..4654ae5db3d8 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/DefaultServlet.java
@@ -32,8 +32,6 @@
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.CompletableFuture;
-import java.util.function.Function;
-import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -64,7 +62,6 @@
import org.eclipse.jetty.io.ByteBufferInputStream;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.server.Context;
-import org.eclipse.jetty.server.HttpStream;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.ResourceService;
import org.eclipse.jetty.server.Response;
@@ -633,17 +630,6 @@ public boolean isSecure()
return _servletRequest.isSecure();
}
- @Override
- public boolean addErrorListener(Predicate onError)
- {
- return false;
- }
-
- @Override
- public void addHttpStreamWrapper(Function wrapper)
- {
- }
-
@Override
public Object removeAttribute(String name)
{
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java
index 475a1ea1551e..ebccf42b098c 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletChannel.java
@@ -83,7 +83,6 @@ public class ServletChannel
private final Listener _combinedListener;
private volatile ServletContextRequest _servletContextRequest;
private volatile boolean _expects100Continue;
- private volatile long _oldIdleTimeout;
private volatile Callback _callback;
// Bytes written after interception (e.g. after compression).
private volatile long _written;
@@ -380,7 +379,6 @@ private void recycle()
_servletContextRequest = null;
_callback = null;
_written = 0;
- _oldIdleTimeout = 0;
}
/**
@@ -838,10 +836,6 @@ public void onCompleted()
if (LOG.isDebugEnabled())
LOG.debug("onCompleted for {} written={}", apiRequest.getRequestURI(), getBytesWritten());
- long idleTO = _configuration.getIdleTimeout();
- if (idleTO >= 0 && getIdleTimeout() != _oldIdleTimeout)
- setIdleTimeout(_oldIdleTimeout);
-
if (getServer().getRequestLog() instanceof CustomRequestLog)
{
CustomRequestLog.LogDetail logDetail = new CustomRequestLog.LogDetail(
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextRequest.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextRequest.java
index 519064b8cd0e..a9144f3f989a 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextRequest.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextRequest.java
@@ -20,6 +20,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeoutException;
import jakarta.servlet.AsyncListener;
import jakarta.servlet.ServletRequest;
@@ -108,6 +109,7 @@ protected ServletContextRequest(
_matchedPath = matchedResource.getMatchedPath();
_response = newServletContextResponse(response);
_sessionManager = sessionManager;
+ addIdleTimeoutListener(this::onIdleTimeout);
}
protected ServletApiRequest newServletApiRequest()
@@ -131,6 +133,11 @@ protected ServletContextResponse newServletContextResponse(Response response)
return new ServletContextResponse(_servletChannel, this, response);
}
+ private boolean onIdleTimeout(TimeoutException timeout)
+ {
+ return _servletChannel.getState().onIdleTimeout(timeout);
+ }
+
public String getDecodedPathInContext()
{
return _decodedPathInContext;
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletRequestState.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletRequestState.java
index bb251ad9c40f..0be875eb667f 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletRequestState.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletRequestState.java
@@ -16,6 +16,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import jakarta.servlet.AsyncListener;
import jakarta.servlet.ServletContext;
@@ -728,6 +729,18 @@ public void asyncError(Throwable failure)
}
}
+ public boolean onIdleTimeout(TimeoutException timeout)
+ {
+ try (AutoLock ignored = lock())
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("onIdleTimeout {}", getStatusStringLocked(), timeout);
+ // TODO this is almost always returning false?!? what about read/write timeouts???
+ // return _state == State.IDLE;
+ return true;
+ }
+ }
+
protected void onError(Throwable th)
{
final AsyncContextEvent asyncEvent;
diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java
index 7eae118d4687..e8f7583bc314 100644
--- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java
+++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/AbstractTest.java
@@ -13,9 +13,11 @@
package org.eclipse.jetty.ee10.test.client.transport;
+import java.io.InputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.security.KeyStore;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
@@ -51,17 +53,23 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
+import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.unixdomain.server.UnixDomainServerConnector;
import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertTrue;
+@ExtendWith(WorkDirExtension.class)
public class AbstractTest
{
+ public WorkDir workDir;
+
protected final HttpConfiguration httpConfig = new HttpConfiguration();
protected SslContextFactory.Server sslContextFactoryServer;
protected Server server;
@@ -140,16 +148,27 @@ protected Server newServer()
return new Server(serverThreads);
}
- protected SslContextFactory.Server newSslContextFactoryServer()
+ protected SslContextFactory.Server newSslContextFactoryServer() throws Exception
{
SslContextFactory.Server ssl = new SslContextFactory.Server();
- ssl.setKeyStorePath("src/test/resources/keystore.p12");
- ssl.setKeyStorePassword("storepwd");
- ssl.setUseCipherSuitesOrder(true);
- ssl.setCipherComparator(HTTP2Cipher.COMPARATOR);
+ configureSslContextFactory(ssl);
return ssl;
}
+ private void configureSslContextFactory(SslContextFactory sslContextFactory) throws Exception
+ {
+ KeyStore keystore = KeyStore.getInstance("PKCS12");
+ try (InputStream is = Files.newInputStream(Path.of("src/test/resources/keystore.p12")))
+ {
+ keystore.load(is, "storepwd".toCharArray());
+ }
+ sslContextFactory.setTrustStore(keystore);
+ sslContextFactory.setKeyStore(keystore);
+ sslContextFactory.setKeyStorePassword("storepwd");
+ sslContextFactory.setUseCipherSuitesOrder(true);
+ sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
+ }
+
protected void startClient(Transport transport) throws Exception
{
QueuedThreadPool clientThreads = new QueuedThreadPool();
@@ -167,7 +186,11 @@ public AbstractConnector newConnector(Transport transport, Server server)
case HTTP, HTTPS, H2C, H2, FCGI ->
new ServerConnector(server, 1, 1, newServerConnectionFactory(transport));
case H3 ->
- new HTTP3ServerConnector(server, sslContextFactoryServer, newServerConnectionFactory(transport));
+ {
+ HTTP3ServerConnector connector = new HTTP3ServerConnector(server, sslContextFactoryServer, newServerConnectionFactory(transport));
+ connector.getQuicConfiguration().setPemWorkDirectory(workDir.getEmptyPathDir());
+ yield connector;
+ }
case UNIX_DOMAIN ->
{
UnixDomainServerConnector connector = new UnixDomainServerConnector(server, 1, 1, newServerConnectionFactory(transport));
@@ -215,16 +238,15 @@ protected ConnectionFactory[] newServerConnectionFactory(Transport transport)
return list.toArray(ConnectionFactory[]::new);
}
- protected SslContextFactory.Client newSslContextFactoryClient()
+ protected SslContextFactory.Client newSslContextFactoryClient() throws Exception
{
SslContextFactory.Client ssl = new SslContextFactory.Client();
- ssl.setKeyStorePath("src/test/resources/keystore.p12");
- ssl.setKeyStorePassword("storepwd");
+ configureSslContextFactory(ssl);
ssl.setEndpointIdentificationAlgorithm(null);
return ssl;
}
- protected HttpClientTransport newHttpClientTransport(Transport transport)
+ protected HttpClientTransport newHttpClientTransport(Transport transport) throws Exception
{
return switch (transport)
{
diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/ServerTimeoutsTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/ServerTimeoutsTest.java
index 3f6a9b1525f2..9e78e606dc83 100644
--- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/ServerTimeoutsTest.java
+++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/java/org/eclipse/jetty/ee10/test/client/transport/ServerTimeoutsTest.java
@@ -60,13 +60,13 @@
// since they may be ignored, so we don't want to remember errors if they are ignored.
// However, this behavior is historically so because of Servlets, and we
// may decide differently for Handlers.
-@Disabled
public class ServerTimeoutsTest extends AbstractTest
{
@ParameterizedTest
@MethodSource("transportsNoFCGI")
public void testBlockingReadWithDelayedFirstContentWithUndelayedDispatchIdleTimeoutFires(Transport transport) throws Exception
{
+ assumeTrue(transport != Transport.H3 && transport != Transport.H2C && transport != Transport.H2); // TODO Fix
testBlockingReadWithDelayedFirstContentIdleTimeoutFires(transport, false);
}
@@ -74,6 +74,7 @@ public void testBlockingReadWithDelayedFirstContentWithUndelayedDispatchIdleTime
@MethodSource("transportsNoFCGI")
public void testBlockingReadWithDelayedFirstContentWithDelayedDispatchIdleTimeoutFires(Transport transport) throws Exception
{
+ assumeTrue(transport != Transport.H3 && transport != Transport.H2C && transport != Transport.H2); // TODO Fix
testBlockingReadWithDelayedFirstContentIdleTimeoutFires(transport, true);
}
@@ -369,6 +370,8 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
@MethodSource("transportsNoFCGI")
public void testBlockingReadWithMinimumDataRateAboveLimit(Transport transport) throws Exception
{
+ assumeTrue(transport != Transport.H3 && transport != Transport.H2C && transport != Transport.H2); // TODO Fix
+
int bytesPerSecond = 20;
httpConfig.setMinRequestDataRate(bytesPerSecond);
CountDownLatch handlerLatch = new CountDownLatch(1);
@@ -413,6 +416,8 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
@MethodSource("transportsNoFCGI")
public void testBlockingReadHttpIdleTimeoutOverridesIdleTimeout(Transport transport) throws Exception
{
+ assumeTrue(transport != Transport.H3); // TODO Fix H3
+
long httpIdleTimeout = 2500;
long idleTimeout = 3 * httpIdleTimeout;
httpConfig.setIdleTimeout(httpIdleTimeout);
@@ -444,7 +449,7 @@ public void testBlockingReadHttpIdleTimeoutOverridesIdleTimeout(Transport transp
@MethodSource("transportsNoFCGI")
public void testAsyncReadHttpIdleTimeoutOverridesIdleTimeout(Transport transport) throws Exception
{
- long httpIdleTimeout = 2500;
+ long httpIdleTimeout = 2000;
long idleTimeout = 3 * httpIdleTimeout;
httpConfig.setIdleTimeout(httpIdleTimeout);
CountDownLatch handlerLatch = new CountDownLatch(1);
@@ -503,6 +508,7 @@ public void onError(Throwable failure)
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
}
+ @Disabled
@ParameterizedTest
@MethodSource("transportsNoFCGI")
public void testIdleTimeoutBeforeReadIsIgnored(Transport transport) throws Exception
@@ -556,6 +562,7 @@ public void onComplete(Result result)
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
+ @Disabled
@ParameterizedTest
@MethodSource("transportsNoFCGI")
public void testBlockingWriteWithMinimumDataRateBelowLimit(Transport transport) throws Exception
diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/resources/keystore.p12 b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-client-transports/src/test/resources/keystore.p12
index 8934437fa14490265dfdb5d9a55bfaa88d039dcd..d96d0667bd6619914779294596679f38f320be7e 100644
GIT binary patch
literal 2774
zcma);X*d*$8pmhGjLE*tU^HVZ9qJ@AmSjnmWUQI8mT1y&Fj}U@QDGL0B}-!)lq?B3
zB-v^T*%fz;tOsKW84-g*$H?tI_dcEHe!BO=`@GNl`@jG9|w>S>rUc$2GhcpnI)B?|(oqa{W6{QDwE3<5x_
zh>BfgV1Wb?Fz^sq7I5)Idt`U9X~CYmhczbI&%tysSK4*PBMl!g>)i7;P*`kk1oIE^#{lRU|q&5S|+tm6UW{b+`I=m
zS0gO*EGmeW)*mr5u$^<~Fcw+C;{JyX&z
z>_%g6o?93L!|fH1%n}C`voRqj8T^XasMQIX-@S$=V#v(W(3uORcJ$=lR`(`@mr;o!
z>H^V3L+>alBIgvLDy2P`k$$3#a#3^DNEohM8Xqvt;v~4{g-Rnq4A*=_`&W}v^8U)`
z(=c{xuZjEb*S8)Mrdokc)+7ZzaaGl@b>$S1x^HNYEIqb>ZaoAJE~2hq4f9eu@%ig`
zg4#nymxu+>E?>v-S0E)9+1#Y5#Hc^Crvi_(mnrxAzPUlT?67db*43nQo7NOpmej)`
z8%yDyia&MizihVNHopx56y>;^%=--9bDJBKOAYD*W~7IEu%`?sa!GraYDvp33SKXb
zX@0K^fl(9hY8h~ZLa=3gcx^Y*S7IE=H_fH6+LN#_!N<&--scYS5C2KzIarGo>-Vj@
z9a`xd#l>l8%u%B_7V&Ffq4dG)XHh}p&%W8^rbE-h2FVF@TKeeL1=<~il{Yr<*J(2%
zQU7Rr)@u%VBoNeYjUubLkF_LMLV;;9ZY72tvhpjmJ&G`+U}=~Yplho~*s54$Mh#Z`RdAjbx;b^Xl%;hR&0n)COWF^>*bxr|
z4S!I@!#UxcG3RRyrHi&5I#=YfI=r9^8v(xY>GMXB$lbY>y&jZbc;j(lI1i0Fv+eBc
zC(Lxa<``F~^W2??;iAWyyMQ~f?%7X*m1ax5<`u-e<=-p&KR7EPbZ_-U-6pWAi(AXS
zHvsZ5YY>+Fh?9x$V!H>vEYOflj-kRH-SUHESe|Ui#
znGiWVGn#p(X6>V9#*HV^QM5O9&~JF#4L%rLpZ9RgtOMFIsas9BjEHj@JyhfJF2m2q
zDP4lPRvy%f-o+7yYX^*R{40Ra!5lV@#yofuaaBc|W{uI8cw@tcUsKU}*48*iOmRiL
zwrQeNI*$RhTRTnsPC7?!TIHF1g6;5a_q_~Iik}|U3FqUC7Q(g%oc*rCenZ(Qe^s2a
zp{{%oGS$JFCN*Yx79lL5EO*D;wx;J(Vz8-c8phVphuS%DSHgnl$*S7;-mGMxJ*bzD=G-T?`+=^{w31!(UKqHT>hDZhMs50N6kEK?LLUQZcFZps!kpm$|>1$YO`->KA$o4e1VD?w^tAKA;Bss=1x2%
zJY~v<6_ETHV~$q-8JA*kjHIeKAOvs=5DXvyZlN{)mJ~2>u)J$v2tgfl^q7IMp0SaE
zv9W;xnhvh|xdau>r-KWBpobtJ;74QqtpNX5NayyPe1R(9uTjJ>LUc;SJ8vvu^ZyBH
zEr*ue^CNB+MyDeoLTEdQN(ZZh*%g0Aymheq66dvq
zHi5C6yBfvsm6KRhT+?hV_35y*_?2r198Vn|Y!`Y!w^WLobvIT&Uuep}i(zp2x3vR-itQ>
zjjJubCy0WZuSD0{4yF&8g)u!Zs=i8(*xNfdP%I4i&aCg4(g}(`+38jAMn*8a3td^e
zfx9E$(@62oZswW<-pXnWTp}^dm5Ky6dM%=tt95hb?Y1ZqQ|V~>{>c)eU$$iPB1a~|
z$hj-gY<~FeQnaKXV?7bP@jAbA&*Og-A)O2wfIR-7%b$1Q2
zT=svLo%(H~P0-meds)NC$hQfnm@SfdI)3MEf%D{a6vG{;NyEl5P#&|+jhsyH8)#GgBV`cM1wp~|&zrC!rm;t&Rz
zr%ry6*@uUe^A0!1y5v+9PRF|3dNj~4QvNA|8mvXqd}#AryHxM5kU|*M`HIV8g=log
zTD;E@lKA3Z25Ma%&2P8FY9y+8kb4r}0H$gwvBE-MeX+k}o=rc=MqX8FMLI5BC6QNO
zq>fGx(3VJ*SG^6--GTvh^%`YqHC8Y`{TlAS@P;}z
zpfCd&<28yR{s_wz9%bxw_a?>ar)p#Eu^;rU>=1TsnNY@8fGMS#v}ZD8H>!IJrCl5gBSSSz6C
zjJ!Zeo<_;eWFEYsOA5Hol}$=UdFCyu9M)+#=BTZKc0yy&Qa^t^5D)?YtJft>xWfHG
z-hKY7$9o^2+FC~UmaiUtJK4YFp9x}b7lPrRba~h>K7~nmwxyu4U$%tQ4cYovMEw&X
C)Bn@}
delta 2573
zcmY+EX*3iJ8-~po!x-yJM6zVhG9&w1vQ8+A+v(HvnNCdA^b}
zH63N|^29N+ODL2t(HbfmO+Q{0`Ww#vh~=4OT3G9XO<;hOW(>1gR?Z5{l?xSG7N=^g
zB{0ZzYFEfRQF`lXt6UOz%gAkdtMcKNRlkTmu+;i3-2<@Q19ASTn53b`#s*)%zpK|7
z(y@@f+i9>S5DwWR@(f#2j0hjtD8r(Uq`idI^I}^KH&Iek$CrG0hMfnR*RtZFKT|9@
zlJd%KdnsSDMe>3n1ey>f0tFz3e)O15B`t{Ejb(?K7Yo~C8uKT1uG8zcm5Q&MA&Qdq
zm-77L`kJzkuofCWqKeXcgX5l|f1g!K#QTH05I@!#x2s27H%dz!Dn|=F2SAvmwO7mQ
zy}yI6Wzp8wW9~gE6FTNN5k-g_UQWppWpLPck#+9Ts`l7+d^zOkk8)fbS$ctJ@D?SO
zM)lx~OaMJe?QhiiruS8DrhYr2y*^oB|Q}JW$kx__Rz^t5puKmS61>o6d^A
zkA>R3%H4kEa~dS@#cCd8#`4jiu7$fjQDp1z-R>qS{?}qpHZz=O36l?=m1yb>G%?7>
z4cL&f`%xQ=Twym+PUAb6g=+AR1%f6c&JfNiuTM`3GaoJ9xH2Q5_evRR5FNumCDJ)r
ztQ|nsODX&!$cp4}w;QU<9uZP(ymWQLzq~U-Ku+Q)w!#RyYnp@TFSF=<$qA$!3Q(ao
zR;JcQb68aq`mvlB7J6RlLzgg%zz^M88yucw|7{5u?`NnnjuCf8*Rpdh}$-iC3dV
zejypvgGoU3V_@Cs<&D?w_V=$ZeWBf+_Y0`qcl=yO=KK_?e4n-BYZxe#3-Ybaims^1
zjrXDw;j7C0#?I;~b^Kh+`YuuWjZrAyMvShkQhl0Y$YZv@uLnyXfq=*N3-Glg0$owtsMQ)DUyptdw-Xr(3f)jo{=;MY;U)(lX0M&g_u+?
zsTLYd=Dn>`M%wKO@A8>1@%n!Jva?FEM=kfXrEkXh@p4oaupy=y+|kKEE>*ZaK%jyo
zco7Y#JB2C)c5YI7-CH!yKC9|+jCz~v;ClF5V0-pPceAN6GJnRt%r_JZ4d2c|+wPns
z)xuO>QnLqN4;Yi*Za_bcUlecxDb|HB?mF6xzRK1Oc;V69x&35ZY1Lvf5#R_Ox%o%*
z=Y6+tEc+#^!hFdTTR|d~1CS_Sz&%LP+InyEb240htGRImrDj=)tXdG-EmbJyrGy{m
z7kof-ZBI*?K4n8dgK6H}PYRcyh0RlB-N$TABCtNmf!r+=c~G71E^W*x`vRF
z#ahDi2G{eCT>zOG49Y0@6$Mo#B}KTJn(CPpE&q`y_{@_AXGj+apgRkVe;UBQ%nAOl
zIgtmxYwtEM$d&ZY!2nq;>MK!U$7kkjGml1t=%js&mhzmp64e&69)H+Kz4fI%`?$Xt
z^El7*FpNJ|dx5SrG32GjpV@3f;+5{1MlnhG0U^?u&4Un*Rfbo;RcuKlx2P0r#yBu&
zQK^VUczs<@j10h`>HVLw7~5Yq2K4sD@C#Ny;gPDFt`1#%3SfgAvj>?d#!rwdZ?c;
zN-|R}9G4~XYSvXGipmErsMS}(PkB`GIYyR@ijTKIs1*YfVv`E#*9#n}4lGi`=@i=G
zS08p+semAqOKpwdQ%gL3vZlEpAhB$t4IfD0%V!6aiLML~emp6tNMsW1W5NWNzJJqh
zRU10aov8m}U%`|wwQ-DJO?UP+o;-%SHper{pqhE1j>RteS+Cs8Hk$~a99cyKwM3eH
z@LsAS#^k~9*AEh+W*yM}`tveFjM^l3+j`xC2Y&TV%H6JAr&de_jm+^<2~X{9qRVRi
zPj8vD=rsLFYC)2-O>u4MAH+GrD`F=MjH6X<2`L~Ovj#6?C#}N6962Y2LWjdq7#V~3
znnP8;J?#(>msu&@!J*-W&mF2M%da?2;Ud+N+Ea{nEUL!&8&f3qgwQ!8&ozqpJv`EPFrrjR
z^rb})LoVUd4#>G^@w+Y-mxqea#Ar_2vjrJvfOdm1pF$)!qD#AQpL1x4^$Goti
zt3<-`XLf|Y=GSOp&&X9gxg#^NsBtz>Q#O!Q%CNn~Ew$!hqer{ghC
zQ-g>;x}?_d_j7!U=pMeg= 0 && _oldIdleTimeout != idleTO)
- setIdleTimeout(idleTO);
if (LOG.isDebugEnabled())
{
diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/RequestTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/RequestTest.java
index 8834bf6b6ef0..f57cf33fd096 100644
--- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/RequestTest.java
+++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/RequestTest.java
@@ -37,8 +37,8 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
import java.util.function.Function;
-import java.util.function.Predicate;
import java.util.stream.Stream;
import jakarta.servlet.MultipartConfigElement;
@@ -2439,9 +2439,8 @@ public void fail(Throwable failure)
}
@Override
- public boolean addErrorListener(Predicate onError)
+ public void addFailureListener(Consumer onFailure)
{
- return false;
}
@Override
diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/ResponseTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/ResponseTest.java
index 83d9cedaa412..cefbae90d2da 100644
--- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/ResponseTest.java
+++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/ResponseTest.java
@@ -31,6 +31,8 @@
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -2387,9 +2389,13 @@ public void fail(Throwable failure)
}
@Override
- public boolean addErrorListener(Predicate onError)
+ public void addIdleTimeoutListener(Predicate onIdleTimeout)
+ {
+ }
+
+ @Override
+ public void addFailureListener(Consumer onFailure)
{
- return false;
}
@Override