diff --git a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-proxy.adoc b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-proxy.adoc
index 45d1e360fa24..9f40bf915605 100644
--- a/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-proxy.adoc
+++ b/documentation/jetty-documentation/src/main/asciidoc/programming-guide/client/http/client-http-proxy.adoc
@@ -37,6 +37,8 @@ Configured in this way, `HttpClient` makes requests to the HTTP proxy (for plain
Proxying is supported for any version of the HTTP protocol.
+The communication between the client and the proxy may be encrypted, so that it would not be possible for another party on the same network as the client to know what servers the client connects to.
+
[[pg-client-http-proxy-socks5]]
===== SOCKS5 Proxy Support
diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
index 25a7e378fb99..51a72f93ebac 100644
--- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
+++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/server/http/HTTPServerDocs.java
@@ -1386,14 +1386,14 @@ protected void onBeforeHandling(Request request)
}
@Override
- protected void onComplete(Request request, Throwable failure)
+ protected void onComplete(Request request, int status, HttpFields headers, Throwable failure)
{
// Retrieve the before handling nanoTime.
long beforeHandlingNanoTime = (long)request.getAttribute("beforeHandlingNanoTime");
- // Record the request processing time.
+ // Record the request processing time and the status that was sent back to the client.
long processingTime = NanoTime.millisSince(beforeHandlingNanoTime);
- System.getLogger("trackTime").log(INFO, "processing request %s took %d ms", request, processingTime);
+ System.getLogger("trackTime").log(INFO, "processing request %s took %d ms and ended with status code %d", request, processingTime, status);
}
}
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java
index f8561b18bcae..4d9171a23fad 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectionPool.java
@@ -434,32 +434,71 @@ public boolean remove(Connection connection)
if (removed)
{
released(connection);
- removed(connection);
+ onRemoved(connection);
}
return removed;
}
+ /**
+ *
Callback method invoked when a new {@link Connection} has been created.
+ *
+ * @param connection the {@link Connection} that has been created
+ * @see #onRemoved(Connection)
+ */
protected void onCreated(Connection connection)
{
}
+ /**
+ * @param connection the {@link Connection} that become idle
+ * @param close whether this pool is closing
+ * @return {@code true} to indicate that the connection is idle, {@code false} otherwise
+ * @deprecated Racy API. Do not use. There is no replacement.
+ */
+ @Deprecated(since = "12.0.8", forRemoval = true)
protected boolean idle(Connection connection, boolean close)
{
return !close;
}
+ /**
+ * @param connection the {@link Connection} that was acquired
+ * @deprecated Racy API. Do not use. There is no replacement.
+ */
+ @Deprecated(since = "12.0.8", forRemoval = true)
protected void acquired(Connection connection)
{
}
+ /**
+ * @param connection the {@link Connection} that was released
+ * @deprecated Racy API. Do not use. There is no replacement.
+ */
+ @Deprecated(since = "12.0.8", forRemoval = true)
protected void released(Connection connection)
{
}
+ /**
+ * @param connection the {@link Connection} that was removed
+ * @deprecated replaced by {@link #onRemoved(Connection)}
+ */
+ @Deprecated(since = "12.0.8", forRemoval = true)
protected void removed(Connection connection)
{
}
+ /**
+ *
Callback method invoked when a {@link Connection} has been removed from this pool.
+ *
+ * @param connection the {@link Connection} that was removed
+ * @see #onCreated(Connection)
+ */
+ protected void onRemoved(Connection connection)
+ {
+ removed(connection);
+ }
+
Collection getIdleConnections()
{
return pool.stream()
@@ -493,7 +532,7 @@ private void close(Pool.Entry entry)
{
if (LOG.isDebugEnabled())
LOG.debug("Removed terminated entry {}", entry);
- removed(connection);
+ onRemoved(connection);
IO.close(connection);
}
if (!entry.isInUse())
@@ -572,12 +611,11 @@ public void succeeded(Connection connection)
pending.decrementAndGet();
reserved.enable(connection, false);
idle(connection, false);
- complete(null);
+ super.succeeded(connection);
proceed();
}
else
{
- // reduce pending on failure and if not multiplexing also reduce demand
failed(new IllegalArgumentException("Invalid connection object: " + connection));
}
}
@@ -587,10 +625,9 @@ public void failed(Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Connection creation failed {}", reserved, x);
- // reduce pending on failure and if not multiplexing also reduce demand
pending.decrementAndGet();
reserved.remove();
- completeExceptionally(x);
+ super.failed(x);
destination.failed(x);
}
}
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
index 2845f1fbf929..e6d717f3aeb8 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
@@ -33,7 +33,7 @@ public abstract class AbstractConnectorHttpClientTransport extends AbstractHttpC
protected AbstractConnectorHttpClientTransport(ClientConnector connector)
{
this.connector = Objects.requireNonNull(connector);
- addBean(connector);
+ installBean(connector);
}
public ClientConnector getClientConnector()
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index 100376aff51d..7edbf36e20b2 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -149,11 +149,11 @@ public HttpClient()
public HttpClient(HttpClientTransport transport)
{
this.transport = Objects.requireNonNull(transport);
- addBean(transport);
+ installBean(transport);
this.connector = ((AbstractHttpClientTransport)transport).getContainedBeans(ClientConnector.class).stream().findFirst().orElseThrow();
- addBean(requestListeners);
- addBean(handlers);
- addBean(decoderFactories);
+ installBean(requestListeners);
+ installBean(handlers);
+ installBean(decoderFactories);
}
public HttpClientTransport getTransport()
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
index 830f6425ea33..8187a32e8738 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/HttpProxy.java
@@ -38,35 +38,92 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/**
+ *
Client-side proxy configuration for HTTP proxying, as specified by
+ * RFC 9110.
+ *
By default the communication between client and proxy happens using
+ * the HTTP/1.1 protocol, but it may be configured to use
+ * also other HTTP protocol versions, such as HTTP/2.
+ */
public class HttpProxy extends ProxyConfiguration.Proxy
{
private static final Logger LOG = LoggerFactory.getLogger(HttpProxy.class);
+ /**
+ *
Creates a new instance with the given HTTP proxy host and port.
+ *
+ * @param host the HTTP proxy host name
+ * @param port the HTTP proxy port
+ */
public HttpProxy(String host, int port)
{
this(new Origin.Address(host, port), false);
}
+ /**
+ *
Creates a new instance with the given HTTP proxy address.
+ *
When {@code secure=true} the communication between the client and the
+ * proxy will be encrypted (using this proxy {@link #getSslContextFactory()}
+ * which typically defaults to that of {@link HttpClient}.
+ *
+ * @param address the HTTP proxy address (host and port)
+ * @param secure whether the communication between the client and the HTTP proxy should be secure
+ */
public HttpProxy(Origin.Address address, boolean secure)
{
this(address, secure, new Origin.Protocol(List.of("http/1.1"), false));
}
+ /**
+ *
Creates a new instance with the given HTTP proxy address and protocol.
+ *
+ * @param address the HTTP proxy address (host and port)
+ * @param secure whether the communication between the client and the HTTP proxy should be secure
+ * @param protocol the protocol to use to communicate with the HTTP proxy
+ */
public HttpProxy(Origin.Address address, boolean secure, Origin.Protocol protocol)
{
this(new Origin(secure ? "https" : "http", address, null, protocol, Transport.TCP_IP), null);
}
+ /**
+ *
Creates a new instance with the given HTTP proxy address and TLS configuration.
+ *
The {@link SslContextFactory} could have a different configuration from the
+ * one configured in {@link HttpClient}, and it is used to communicate with the HTTP
+ * proxy only (not to communicate with the servers).
+ *
+ * @param address the HTTP proxy address (host and port)
+ * @param sslContextFactory the {@link SslContextFactory.Client} to use to communicate with the HTTP proxy
+ */
public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory)
{
this(address, sslContextFactory, new Origin.Protocol(List.of("http/1.1"), false));
}
+ /**
+ *
Creates a new instance with the given HTTP proxy address, TLS configuration and protocol.
+ *
The {@link SslContextFactory} could have a different configuration from the
+ * one configured in {@link HttpClient} and it is used to communicate with the HTTP
+ * proxy only (not to communicate with the servers).
+ *
+ * @param address the HTTP proxy address (host and port)
+ * @param sslContextFactory the {@link SslContextFactory.Client} to use to communicate with the HTTP proxy
+ * @param protocol the protocol to use to communicate with the HTTP proxy
+ */
public HttpProxy(Origin.Address address, SslContextFactory.Client sslContextFactory, Origin.Protocol protocol)
{
this(new Origin(sslContextFactory == null ? "http" : "https", address, null, protocol, Transport.TCP_IP), sslContextFactory);
}
+ /**
+ *
Creates a new instance with the given HTTP proxy {@link Origin} and TLS configuration.
+ *
The {@link SslContextFactory} could have a different configuration from the
+ * one configured in {@link HttpClient} and it is used to communicate with the HTTP
+ * proxy only (not to communicate with the servers).
+ *
+ * @param origin the HTTP proxy {@link Origin} information
+ * @param sslContextFactory the {@link SslContextFactory.Client} to use to communicate with the HTTP proxy
+ */
public HttpProxy(Origin origin, SslContextFactory.Client sslContextFactory)
{
super(origin, sslContextFactory);
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 855631099ffb..2fb07066cdf6 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
@@ -33,13 +33,35 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+/**
+ *
Client-side proxy configuration for SOCKS4, a de-facto standard.
+ *
Consider using SOCK5 instead, a protocol that has been standardized
+ * by IETF.
+ *
+ * @see Socks5Proxy
+ */
public class Socks4Proxy extends ProxyConfiguration.Proxy
{
+ /**
+ *
Creates a new instance with the given SOCKS4 proxy host and port.
+ *
+ * @param host the SOCKS4 proxy host name
+ * @param port the SOCKS4 proxy port
+ */
public Socks4Proxy(String host, int port)
{
this(new Origin.Address(host, port), false);
}
+ /**
+ *
Creates a new instance with the given SOCKS4 proxy address.
+ *
When {@code secure=true} the communication between the client and the
+ * proxy will be encrypted (using this proxy {@link #getSslContextFactory()}
+ * which typically defaults to that of {@link HttpClient}.
+ *
+ * @param address the SOCKS4 proxy address (host and port)
+ * @param secure whether the communication between the client and the SOCKS4 proxy should be secure
+ */
public Socks4Proxy(Origin.Address address, boolean secure)
{
super(address, secure, null, null);
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 f4e353ec3b08..56a800af7784 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
@@ -56,11 +56,26 @@ public class Socks5Proxy extends Proxy
private final Map authentications = new LinkedHashMap<>();
+ /**
+ *
Creates a new instance with the given SOCKS5 proxy host and port.
+ *
+ * @param host the SOCKS5 proxy host name
+ * @param port the SOCKS5 proxy port
+ */
public Socks5Proxy(String host, int port)
{
this(new Origin.Address(host, port), false);
}
+ /**
+ *
Creates a new instance with the given SOCKS5 proxy address.
+ *
When {@code secure=true} the communication between the client and the
+ * proxy will be encrypted (using this proxy {@link #getSslContextFactory()}
+ * which typically defaults to that of {@link HttpClient}.
+ *
+ * @param address the SOCKS5 proxy address (host and port)
+ * @param secure whether the communication between the client and the SOCKS5 proxy should be secure
+ */
public Socks5Proxy(Origin.Address address, boolean secure)
{
super(address, secure, null, null);
diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportDynamic.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportDynamic.java
index 8f7da5848174..31f2d5fbcf3e 100644
--- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportDynamic.java
+++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportDynamic.java
@@ -115,7 +115,7 @@ public HttpClientTransportDynamic(ClientConnector connector, ClientConnectionFac
{
super(connector);
this.infos = infos.length == 0 ? List.of(HttpClientConnectionFactory.HTTP11) : List.of(infos);
- this.infos.forEach(this::addBean);
+ this.infos.forEach(this::installBean);
setConnectionPoolFactory(destination ->
new MultiplexConnectionPool(destination, destination.getHttpClient().getMaxConnectionsPerDestination(), 1)
);
diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
index f4ae856e9d7e..e03b46f293c4 100644
--- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
+++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
@@ -494,7 +494,7 @@ protected void onCreated(Connection connection)
}
@Override
- protected void removed(Connection connection)
+ protected void onRemoved(Connection connection)
{
poolRemoveCounter.incrementAndGet();
}
@@ -548,7 +548,7 @@ protected void onCreated(Connection connection)
}
@Override
- protected void removed(Connection connection)
+ protected void onRemoved(Connection connection)
{
poolRemoveCounter.incrementAndGet();
}
diff --git a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java
index 44278c50c3a2..01d395d5699c 100644
--- a/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java
+++ b/jetty-core/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ScanningAppProvider.java
@@ -90,7 +90,7 @@ protected ScanningAppProvider()
protected ScanningAppProvider(FilenameFilter filter)
{
_filenameFilter = filter;
- addBean(_appMap);
+ installBean(_appMap);
}
@Override
diff --git a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java
index 37af842dad5c..7c3e202eea36 100644
--- a/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java
+++ b/jetty-core/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/DelegatingThreadPool.java
@@ -33,7 +33,7 @@ public DelegatingThreadPool(Executor executor)
{
_executor = executor;
_tryExecutor = TryExecutor.asTryExecutor(executor);
- addBean(_executor);
+ installBean(_executor);
}
public Executor getExecutor()
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCache.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCache.java
index 5e6c34961b62..540023cd1c1c 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCache.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCache.java
@@ -49,6 +49,12 @@ public CookieCache(CookieCompliance compliance)
_parser = CookieParser.newParser(this, compliance, this);
}
+ @Deprecated(forRemoval = true)
+ public CookieCache(CookieCompliance compliance, ComplianceViolation.Listener complianceListener)
+ {
+ this(compliance);
+ }
+
@Override
public void onComplianceViolation(ComplianceViolation.Event event)
{
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java
index 99c645f7d3cb..88246cda0cf4 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java
@@ -19,6 +19,7 @@
import java.util.TimeZone;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.thread.ThreadIdPool;
/**
* ThreadLocal Date formatters for HTTP style dates.
@@ -37,14 +38,7 @@ public class DateGenerator
static final String[] MONTHS =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"};
- private static final ThreadLocal __dateGenerator = new ThreadLocal()
- {
- @Override
- protected DateGenerator initialValue()
- {
- return new DateGenerator();
- }
- };
+ private static final ThreadIdPool __dateGenerator = new ThreadIdPool<>();
public static final String __01Jan1970 = DateGenerator.formatDate(0);
@@ -56,7 +50,7 @@ protected DateGenerator initialValue()
*/
public static String formatDate(long date)
{
- return __dateGenerator.get().doFormatDate(date);
+ return __dateGenerator.apply(DateGenerator::new, DateGenerator::doFormatDate, date);
}
/**
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java
index a1e75007918a..2af820fb3ac1 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java
@@ -18,6 +18,8 @@
import java.util.Locale;
import java.util.TimeZone;
+import org.eclipse.jetty.util.thread.ThreadIdPool;
+
/**
* ThreadLocal data parsers for HTTP style dates
*/
@@ -47,17 +49,10 @@ public class DateParser
public static long parseDate(String date)
{
- return DATE_PARSER.get().parse(date);
+ return DATE_PARSER.apply(DateParser::new, DateParser::parse, date);
}
- private static final ThreadLocal DATE_PARSER = new ThreadLocal()
- {
- @Override
- protected DateParser initialValue()
- {
- return new DateParser();
- }
- };
+ private static final ThreadIdPool DATE_PARSER = new ThreadIdPool<>();
final SimpleDateFormat[] _dateReceive = new SimpleDateFormat[DATE_RECEIVE_FMT.length];
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
index 867b33983cd1..f7907a83f765 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
@@ -542,6 +542,7 @@ private boolean quickStartRequestLine(ByteBuffer buffer)
buffer.position(position + 2 * Long.BYTES);
_methodString = HttpMethod.GET.asString();
_version = HttpVersion.HTTP_1_1;
+ _fieldCache.prepare();
setState(State.HEADER);
_requestHandler.startRequest(_methodString, "/", _version);
return true;
@@ -551,6 +552,7 @@ private boolean quickStartRequestLine(ByteBuffer buffer)
buffer.position(position + 2 * Long.BYTES);
_methodString = HttpMethod.GET.asString();
_version = HttpVersion.HTTP_1_0;
+ _fieldCache.prepare();
setState(State.HEADER);
_requestHandler.startRequest(_methodString, "/", _version);
return true;
diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/ClientConnectionFactoryOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/ClientConnectionFactoryOverHTTP2.java
index 567f27db9392..2958bf141ced 100644
--- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/ClientConnectionFactoryOverHTTP2.java
+++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/ClientConnectionFactoryOverHTTP2.java
@@ -43,7 +43,7 @@ public class ClientConnectionFactoryOverHTTP2 extends ContainerLifeCycle impleme
public ClientConnectionFactoryOverHTTP2(HTTP2Client http2Client)
{
this.http2Client = http2Client;
- addBean(http2Client);
+ installBean(http2Client);
}
@Override
diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java
index cd56a974030e..a5178b522418 100644
--- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java
+++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/HttpClientTransportOverHTTP2.java
@@ -53,7 +53,7 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
public HttpClientTransportOverHTTP2(HTTP2Client http2Client)
{
this.http2Client = http2Client;
- addBean(http2Client);
+ installBean(http2Client);
setConnectionPoolFactory(destination ->
{
HttpClient httpClient = getHttpClient();
diff --git a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java
index 77263270b914..b49687bac794 100644
--- a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java
+++ b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2Client.java
@@ -127,7 +127,7 @@ public HTTP2Client()
public HTTP2Client(ClientConnector connector)
{
this.connector = connector;
- addBean(connector);
+ installBean(connector);
}
public ClientConnector getClientConnector()
diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
index 0b0a753d2495..43e24fea1183 100644
--- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
+++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
@@ -126,8 +126,8 @@ public HTTP2Session(Scheduler scheduler, EndPoint endPoint, Parser parser, Gener
this.recvWindow.set(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
this.writeThreshold = 32 * 1024;
this.pushEnabled = true; // SPEC: by default, push is enabled.
- addBean(flowControl);
- addBean(flusher);
+ installBean(flowControl);
+ installBean(flusher);
}
@Override
diff --git a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
index bcd57d9172ec..3b69140b4d76 100644
--- a/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
+++ b/jetty-core/jetty-http2/jetty-http2-server/src/main/java/org/eclipse/jetty/http2/server/AbstractHTTP2ServerConnectionFactory.java
@@ -92,9 +92,9 @@ protected AbstractHTTP2ServerConnectionFactory(@Name("config") HttpConfiguration
if (!isProtocolSupported(p))
throw new IllegalArgumentException("Unsupported HTTP2 Protocol variant: " + p);
}
- addBean(sessionContainer);
+ installBean(sessionContainer);
this.httpConfiguration = Objects.requireNonNull(httpConfiguration);
- addBean(httpConfiguration);
+ installBean(httpConfiguration);
setInputBufferSize(Frame.DEFAULT_MAX_SIZE + Frame.HEADER_LENGTH);
setUseInputDirectByteBuffers(httpConfiguration.isUseInputDirectByteBuffers());
setUseOutputDirectByteBuffers(httpConfiguration.isUseOutputDirectByteBuffers());
diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DynamicTableTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DynamicTableTest.java
index a9d0830aa987..ea06bac6e362 100644
--- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DynamicTableTest.java
+++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/DynamicTableTest.java
@@ -129,4 +129,57 @@ public void onHeaders(Stream stream, HeadersFrame frame)
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
+
+ @ParameterizedTest
+ @CsvSource({"0,-1", "-1,0", "0,0"})
+ public void testMaxTableCapacityZero(int clientMaxCapacity, int serverMaxCapacity) throws Exception
+ {
+ start(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ callback.succeeded();
+ return true;
+ }
+ });
+
+ if (clientMaxCapacity >= 0)
+ {
+ http2Client.setMaxDecoderTableCapacity(clientMaxCapacity);
+ http2Client.setMaxEncoderTableCapacity(clientMaxCapacity);
+ }
+ if (serverMaxCapacity >= 0)
+ {
+ connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class).setMaxEncoderTableCapacity(serverMaxCapacity);
+ connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class).setMaxDecoderTableCapacity(serverMaxCapacity);
+ }
+
+ CountDownLatch serverPreface = new CountDownLatch(1);
+ Session session = newClientSession(new Session.Listener()
+ {
+ @Override
+ public void onSettings(Session session, SettingsFrame frame)
+ {
+ serverPreface.countDown();
+ }
+ });
+ assertTrue(serverPreface.await(5, TimeUnit.SECONDS));
+
+ MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
+ HeadersFrame frame = new HeadersFrame(metaData, null, true);
+ CountDownLatch latch = new CountDownLatch(1);
+ session.newStream(frame, new Promise.Adapter<>(), new Stream.Listener()
+ {
+ @Override
+ public void onHeaders(Stream stream, HeadersFrame frame)
+ {
+ MetaData.Response response = (MetaData.Response)frame.getMetaData();
+ assertEquals(200, response.getStatus());
+ latch.countDown();
+ }
+ });
+
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
}
diff --git a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MultiplexedConnectionPoolTest.java b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MultiplexedConnectionPoolTest.java
index c28aa61ad149..c0a383b1e10e 100644
--- a/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MultiplexedConnectionPoolTest.java
+++ b/jetty-core/jetty-http2/jetty-http2-tests/src/test/java/org/eclipse/jetty/http2/tests/MultiplexedConnectionPoolTest.java
@@ -121,7 +121,7 @@ protected void onCreated(Connection connection)
}
@Override
- protected void removed(Connection connection)
+ protected void onRemoved(Connection connection)
{
poolRemoveCounter.incrementAndGet();
}
@@ -226,7 +226,7 @@ protected void onCreated(Connection connection)
}
@Override
- protected void removed(Connection connection)
+ protected void onRemoved(Connection connection)
{
poolRemoveCounter.incrementAndGet();
}
@@ -301,7 +301,7 @@ protected void onCreated(Connection connection)
}
@Override
- protected void removed(Connection connection)
+ protected void onRemoved(Connection connection)
{
poolRemoveCounter.incrementAndGet();
}
@@ -372,7 +372,7 @@ protected void onCreated(Connection connection)
}
@Override
- protected void removed(Connection connection)
+ protected void onRemoved(Connection connection)
{
poolRemoveCounter.incrementAndGet();
}
diff --git a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/ClientConnectionFactoryOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/ClientConnectionFactoryOverHTTP3.java
index f6fa9a5a6dcd..a27ee788578f 100644
--- a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/ClientConnectionFactoryOverHTTP3.java
+++ b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/ClientConnectionFactoryOverHTTP3.java
@@ -35,7 +35,7 @@ public class ClientConnectionFactoryOverHTTP3 extends ContainerLifeCycle impleme
public ClientConnectionFactoryOverHTTP3(HTTP3Client http3Client)
{
- addBean(http3Client);
+ installBean(http3Client);
}
@Override
diff --git a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java
index ef2b3bc65234..490ae2970f48 100644
--- a/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java
+++ b/jetty-core/jetty-http3/jetty-http3-client-transport/src/main/java/org/eclipse/jetty/http3/client/transport/HttpClientTransportOverHTTP3.java
@@ -49,7 +49,7 @@ public class HttpClientTransportOverHTTP3 extends AbstractHttpClientTransport im
public HttpClientTransportOverHTTP3(HTTP3Client http3Client)
{
this.http3Client = Objects.requireNonNull(http3Client);
- addBean(http3Client);
+ installBean(http3Client);
setConnectionPoolFactory(destination ->
{
HttpClient httpClient = getHttpClient();
diff --git a/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/ClientHTTP3Session.java b/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/ClientHTTP3Session.java
index 50c87abeeecb..34869280a893 100644
--- a/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/ClientHTTP3Session.java
+++ b/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/ClientHTTP3Session.java
@@ -58,7 +58,7 @@ public ClientHTTP3Session(HTTP3Configuration configuration, ClientQuicSession qu
super(quicSession);
this.configuration = configuration;
session = new HTTP3SessionClient(this, listener, promise);
- addBean(session);
+ installBean(session);
session.setStreamIdleTimeout(configuration.getStreamIdleTimeout());
if (LOG.isDebugEnabled())
@@ -69,7 +69,7 @@ public ClientHTTP3Session(HTTP3Configuration configuration, ClientQuicSession qu
InstructionFlusher encoderInstructionFlusher = new InstructionFlusher(quicSession, encoderEndPoint, EncoderStreamConnection.STREAM_TYPE);
encoder = new QpackEncoder(new InstructionHandler(encoderInstructionFlusher));
encoder.setMaxHeadersSize(configuration.getMaxRequestHeadersSize());
- addBean(encoder);
+ installBean(encoder);
if (LOG.isDebugEnabled())
LOG.debug("created encoder stream #{} on {}", encoderStreamId, encoderEndPoint);
@@ -77,19 +77,19 @@ public ClientHTTP3Session(HTTP3Configuration configuration, ClientQuicSession qu
QuicStreamEndPoint decoderEndPoint = openInstructionEndPoint(decoderStreamId);
InstructionFlusher decoderInstructionFlusher = new InstructionFlusher(quicSession, decoderEndPoint, DecoderStreamConnection.STREAM_TYPE);
decoder = new QpackDecoder(new InstructionHandler(decoderInstructionFlusher));
- addBean(decoder);
+ installBean(decoder);
if (LOG.isDebugEnabled())
LOG.debug("created decoder stream #{} on {}", decoderStreamId, decoderEndPoint);
long controlStreamId = newStreamId(StreamType.CLIENT_UNIDIRECTIONAL);
QuicStreamEndPoint controlEndPoint = openControlEndPoint(controlStreamId);
controlFlusher = new ControlFlusher(quicSession, controlEndPoint, true);
- addBean(controlFlusher);
+ installBean(controlFlusher);
if (LOG.isDebugEnabled())
LOG.debug("created control stream #{} on {}", controlStreamId, controlEndPoint);
messageFlusher = new MessageFlusher(quicSession.getByteBufferPool(), encoder, configuration.isUseOutputDirectByteBuffers());
- addBean(messageFlusher);
+ installBean(messageFlusher);
}
public QpackDecoder getQpackDecoder()
diff --git a/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java b/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java
index b8bfe7f32010..922c051ed3e4 100644
--- a/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java
+++ b/jetty-core/jetty-http3/jetty-http3-client/src/main/java/org/eclipse/jetty/http3/client/HTTP3Client.java
@@ -146,7 +146,7 @@ public HTTP3Client(ClientQuicConfiguration quicConfiguration, ClientConnector co
{
this.quicConfiguration = quicConfiguration;
this.connector = connector;
- addBean(connector);
+ installBean(connector);
connector.setSslContextFactory(quicConfiguration.getSslContextFactory());
// Allow the mandatory unidirectional streams, plus pushed streams.
quicConfiguration.setMaxUnidirectionalRemoteStreams(48);
diff --git a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java
index 4cd60a409829..0bf47b12e260 100644
--- a/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java
+++ b/jetty-core/jetty-http3/jetty-http3-server/src/main/java/org/eclipse/jetty/http3/server/internal/ServerHTTP3Session.java
@@ -57,7 +57,7 @@ public ServerHTTP3Session(HTTP3Configuration configuration, ServerQuicSession qu
super(quicSession);
this.configuration = configuration;
session = new HTTP3SessionServer(this, listener);
- addBean(session);
+ installBean(session);
session.setStreamIdleTimeout(configuration.getStreamIdleTimeout());
if (LOG.isDebugEnabled())
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferAggregator.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferAggregator.java
index 5670d08dc603..1dd074703e25 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferAggregator.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ByteBufferAggregator.java
@@ -16,6 +16,7 @@
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.TypeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -103,7 +104,7 @@ private void tryExpandBufferCapacity(int remaining)
if (remaining <= capacityLeft)
return;
int need = remaining - capacityLeft;
- _currentSize = Math.min(_maxSize, ceilToNextPowerOfTwo(_currentSize + need));
+ _currentSize = Math.min(_maxSize, TypeUtil.ceilToNextPowerOfTwo(_currentSize + need));
if (_retainableByteBuffer != null)
{
@@ -116,12 +117,6 @@ private void tryExpandBufferCapacity(int remaining)
}
}
- private static int ceilToNextPowerOfTwo(int val)
- {
- int result = 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(val - 1));
- return result > 0 ? result : Integer.MAX_VALUE;
- }
-
/**
* Takes the buffer out of the aggregator. Once the buffer has been taken out,
* the aggregator resets itself and a new buffer will be acquired from the pool
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
index 0d4a4bdf90da..b08dac5a5837 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
@@ -122,7 +122,7 @@ public ClientConnector()
public ClientConnector(Configurator configurator)
{
this.configurator = Objects.requireNonNull(configurator);
- addBean(configurator);
+ installBean(configurator);
configurator.addBean(this, false);
}
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java
index 265e890f8bb6..c78d7bb7bf94 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java
@@ -96,7 +96,7 @@ public ManagedSelector(SelectorManager selectorManager, int id)
SelectorProducer producer = new SelectorProducer();
Executor executor = selectorManager.getExecutor();
_strategy = new AdaptiveExecutionStrategy(producer, executor);
- addBean(_strategy, true);
+ installBean(_strategy, true);
}
public Selector getSelector()
diff --git a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java b/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java
index 0a6f96a06e54..fcd9049c990d 100644
--- a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java
+++ b/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdConfiguration.java
@@ -136,7 +136,7 @@ public OpenIdConfiguration(@Name("issuer") String issuer,
if (this.issuer == null)
throw new IllegalArgumentException("Issuer was not configured");
- addBean(this.httpClient);
+ installBean(this.httpClient);
}
@Override
diff --git a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java b/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java
index dc783c3f8098..f4dcb0d61404 100644
--- a/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java
+++ b/jetty-core/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdLoginService.java
@@ -58,8 +58,8 @@ public OpenIdLoginService(OpenIdConfiguration configuration, LoginService loginS
{
this.configuration = Objects.requireNonNull(configuration);
this.loginService = loginService;
- addBean(this.configuration);
- addBean(this.loginService);
+ installBean(this.configuration);
+ installBean(this.loginService);
setAuthenticateNewUsers(configuration.isAuthenticateNewUsers());
}
diff --git a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/util/Util.java b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/util/Util.java
index 7aa2fa7b7c47..282de78f31db 100644
--- a/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/util/Util.java
+++ b/jetty-core/jetty-osgi/src/main/java/org/eclipse/jetty/osgi/util/Util.java
@@ -20,7 +20,6 @@
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolSession.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolSession.java
index 7eb2fd1a48b2..452fdcbacc02 100644
--- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolSession.java
+++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/ProtocolSession.java
@@ -48,7 +48,7 @@ public ProtocolSession(QuicSession session)
{
this.session = session;
this.strategy = new AdaptiveExecutionStrategy(producer, session.getExecutor());
- addBean(strategy);
+ installBean(strategy);
}
public QuicSession getQuicSession()
diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java
index 664522383b34..c853fd1aec86 100644
--- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java
+++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/QuicSession.java
@@ -82,7 +82,7 @@ protected QuicSession(Executor executor, Scheduler scheduler, ByteBufferPool byt
this.quicheConnection = quicheConnection;
this.connection = connection;
this.flusher = new Flusher(scheduler);
- addBean(flusher);
+ installBean(flusher);
this.remoteAddress = remoteAddress;
Arrays.setAll(ids, i -> new AtomicLong());
}
diff --git a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java
index 7b90dafc1405..bfb4fb6d2ee4 100644
--- a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java
+++ b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common/src/main/java/org/eclipse/jetty/quic/quiche/Quiche.java
@@ -89,7 +89,10 @@ interface quiche_error
QUICHE_ERR_OUT_OF_IDENTIFIERS = -18,
// Error in key update.
- QUICHE_ERR_KEY_UPDATE = -19;
+ QUICHE_ERR_KEY_UPDATE = -19,
+
+ // The peer sent more data in CRYPTO frames than we can buffer.
+ QUICHE_ERR_CRYPTO_BUFFER_EXCEEDED = -20;
static String errToString(long err)
{
@@ -131,6 +134,8 @@ static String errToString(long err)
return "QUICHE_ERR_OUT_OF_IDENTIFIERS";
if (err == QUICHE_ERR_KEY_UPDATE)
return "QUICHE_ERR_KEY_UPDATE";
+ if (err == QUICHE_ERR_CRYPTO_BUFFER_EXCEEDED)
+ return "QUICHE_ERR_CRYPTO_BUFFER_EXCEEDED";
return "?? " + err;
}
}
diff --git a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java
index 9c1990690c4d..e7f9ead641c0 100644
--- a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java
+++ b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign-incubator/src/main/java/org/eclipse/jetty/quic/quiche/foreign/incubator/quiche_h.java
@@ -33,7 +33,7 @@ public class quiche_h
{
// This interface is a translation of the quiche.h header of a specific version.
// It needs to be reviewed each time the native lib version changes.
- private static final String EXPECTED_QUICHE_VERSION = "0.20.0";
+ private static final String EXPECTED_QUICHE_VERSION = "0.20.1";
public static final byte C_FALSE = 0;
public static final byte C_TRUE = 1;
diff --git a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java
index 2c321f0e8624..9b6f7ba3392f 100644
--- a/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java
+++ b/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-jna/src/main/java/org/eclipse/jetty/quic/quiche/jna/LibQuiche.java
@@ -31,7 +31,7 @@ public interface LibQuiche extends Library
{
// This interface is a translation of the quiche.h header of a specific version.
// It needs to be reviewed each time the native lib version changes.
- String EXPECTED_QUICHE_VERSION = "0.20.0";
+ String EXPECTED_QUICHE_VERSION = "0.20.1";
// The charset used to convert java.lang.String to char * and vice versa.
Charset CHARSET = StandardCharsets.UTF_8;
diff --git a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
index 82648394030a..72660597b355 100644
--- a/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
+++ b/jetty-core/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteHandler.java
@@ -60,7 +60,7 @@ public RewriteHandler(Handler handler, RuleContainer rules)
{
super(handler);
_rules = rules;
- addBean(_rules);
+ installBean(_rules);
}
/**
diff --git a/jetty-core/jetty-security/pom.xml b/jetty-core/jetty-security/pom.xml
index 1d9e19052ed5..bc725cb5ef56 100644
--- a/jetty-core/jetty-security/pom.xml
+++ b/jetty-core/jetty-security/pom.xml
@@ -12,7 +12,7 @@
The common Jetty security implementation
- 2.1.5
+ 2.1.62.0.0.AM27${project.groupId}.securityorg.eclipse.jetty.security.*
diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractLoginService.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractLoginService.java
index af5fb04a3a5d..a04def5f1386 100644
--- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractLoginService.java
+++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractLoginService.java
@@ -40,7 +40,7 @@ public abstract class AbstractLoginService extends ContainerLifeCycle implements
protected AbstractLoginService()
{
- addBean(_identityService);
+ installBean(_identityService);
}
@Override
diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SPNEGOLoginService.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SPNEGOLoginService.java
index ee45735ba79c..ec95a5fdd9a7 100644
--- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SPNEGOLoginService.java
+++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SPNEGOLoginService.java
@@ -67,7 +67,7 @@ public SPNEGOLoginService(String realm, LoginService loginService)
{
_realm = realm;
_loginService = loginService;
- addBean(_loginService);
+ installBean(_loginService);
}
/**
diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java
index 6d16baf2bda7..eba634c5bed7 100644
--- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java
+++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java
@@ -98,7 +98,7 @@ protected SecurityHandler()
protected SecurityHandler(Handler handler)
{
super(handler);
- addBean(new DumpableCollection("knownAuthenticatorFactories", __knownAuthenticatorFactories));
+ installBean(new DumpableCollection("knownAuthenticatorFactories", __knownAuthenticatorFactories));
}
/**
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
index b50095800557..5be4f03d2a35 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java
@@ -181,13 +181,13 @@ public AbstractConnector(
_server = Objects.requireNonNull(server);
_executor = executor != null ? executor : _server.getThreadPool();
- addBean(_executor, executor != null);
+ installBean(_executor, executor != null);
_scheduler = scheduler != null ? scheduler : _server.getScheduler();
- addBean(_scheduler, scheduler != null);
+ installBean(_scheduler, scheduler != null);
_bufferPool = bufferPool != null ? bufferPool : server.getByteBufferPool();
- addBean(_bufferPool, bufferPool != null);
+ installBean(_bufferPool, bufferPool != null);
for (ConnectionFactory factory : factories)
{
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
index da8c27853471..50d00d0ba77d 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/AllowedResourceAliasChecker.java
@@ -27,6 +27,7 @@
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.resource.Resources;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -158,6 +159,11 @@ protected boolean check(String pathInContext, Path path)
protected boolean check(String pathInContext, Resource resource)
{
+ // If there is a single Path available, check it
+ Path path = resource.getPath();
+ if (path != null && Files.exists(path))
+ return check(pathInContext, path);
+
// Allow any aliases (symlinks, 8.3, casing, etc.) so long as
// the resulting real file is allowed.
for (Resource r : resource)
@@ -186,7 +192,7 @@ protected boolean isAllowed(Path path)
for (String protectedTarget : _protected)
{
Resource p = _baseResource.resolve(protectedTarget);
- if (p == null)
+ if (Resources.missing(p))
continue;
for (Resource r : p)
{
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java
index c3c04f910a12..2c9f3e716c67 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java
@@ -336,7 +336,7 @@ public CustomRequestLog(RequestLog.Writer writer, String formatString)
{
_formatString = formatString;
_requestLogWriter = writer;
- addBean(_requestLogWriter);
+ installBean(_requestLogWriter);
try
{
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/DetectorConnectionFactory.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/DetectorConnectionFactory.java
index befd3afe2f36..c68a69e6324c 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/DetectorConnectionFactory.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/DetectorConnectionFactory.java
@@ -49,7 +49,7 @@ public DetectorConnectionFactory(Detecting... detectingConnectionFactories)
_detectingConnectionFactories = Arrays.asList(detectingConnectionFactories);
for (Detecting detectingConnectionFactory : detectingConnectionFactories)
{
- addBean(detectingConnectionFactory);
+ installBean(detectingConnectionFactory);
}
}
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
index 691a741f67ca..3344f065d25c 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Handler.java
@@ -355,7 +355,7 @@ default Singleton getTail()
}
/**
- *
Utility method to perform sanity checks before adding the given {@code Handler} to
+ *
Utility method to perform sanity checks before updating the given {@code Handler} to
* the given {@code Singleton}, typically used in implementations of {@link #setHandler(Handler)}.
Update the beans on the {@code Singleton} if it is a {@link ContainerLifeCycle}
*
+ * @param singleton the {@code Singleton} to set the {@code Handler}
+ * @param handler the {@code Handler} to set
+ * @see #checkHandler(Singleton, Handler)
+ * @return The {@code Handler} to set
+ */
+ static Handler updateHandler(Singleton singleton, Handler handler)
+ {
+ // check state
+ checkHandler(singleton, handler);
+
+ if (singleton instanceof org.eclipse.jetty.util.component.ContainerLifeCycle container)
+ container.updateBean(singleton.getHandler(), handler);
+
+ return handler;
+ }
+
+ /**
+ *
Utility method to perform sanity checks on a {{@link Handler} to be added to
+ * the given {@code Singleton}.
+ *
The sanity checks are:
+ *
+ *
Check for the server start state and whether the invocation type is compatible
+ *
Check for {@code Handler} loops
+ *
Sets the {@code Server} on the {@code Handler}
+ *
Update the beans on the {@code Singleton} if it is a {@link ContainerLifeCycle}
+ *
*
- * @param wrapper the {@code Singleton} to set the {@code Handler}
+ * @param singleton the {@code Singleton} to set the {@code Handler}
* @param handler the {@code Handler} to set
* @return The {@code Handler} to set
*/
- static Handler updateHandler(Singleton wrapper, Handler handler)
+ static Handler checkHandler(Singleton singleton, Handler handler)
{
// check state
- Server server = wrapper.getServer();
+ Server server = singleton.getServer();
// If the collection is changed whilst started, then the risk is that if we switch from NON_BLOCKING to BLOCKING
// whilst the execution strategy may have already dispatched the very last available thread, thinking it would
@@ -386,16 +412,13 @@ static Handler updateHandler(Singleton wrapper, Handler handler)
}
// Check for loops.
- if (handler == wrapper || (handler instanceof Handler.Container container &&
- container.getDescendants().contains(wrapper)))
+ if (handler == singleton || (handler instanceof Handler.Container container &&
+ container.getDescendants().contains(singleton)))
throw new IllegalStateException("Handler loop");
if (handler != null && server != null)
handler.setServer(server);
- if (wrapper instanceof org.eclipse.jetty.util.component.ContainerLifeCycle container)
- container.updateBean(wrapper.getHandler(), handler);
-
return handler;
}
}
@@ -692,7 +715,8 @@ public Wrapper(Handler handler)
public Wrapper(boolean dynamic, Handler handler)
{
super(dynamic);
- _handler = handler == null ? null : Singleton.updateHandler(this, handler);
+ _handler = handler == null ? null : Singleton.checkHandler(this, handler);
+ installBean(_handler);
}
@Override
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java
index 82b373364c15..30b5d9b5067a 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java
@@ -46,7 +46,7 @@ public HttpConnectionFactory(@Name("config") HttpConfiguration config)
{
super(HttpVersion.HTTP_1_1.asString());
_config = Objects.requireNonNull(config);
- addBean(_config);
+ installBean(_config);
setUseInputDirectByteBuffers(_config.isUseInputDirectByteBuffers());
setUseOutputDirectByteBuffers(_config.isUseOutputDirectByteBuffers());
}
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java
index d65301fbf588..52d0d74f0aad 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java
@@ -267,7 +267,7 @@ public void setLowResourceChecks(Set lowResourceChecks)
public void addLowResourceCheck(LowResourceCheck lowResourceCheck)
{
- addBean(lowResourceCheck);
+ installBean(lowResourceCheck);
this._lowResourceChecks.add(lowResourceCheck);
}
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
index bed6d6fbcffa..855d0a2be91d 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java
@@ -115,7 +115,7 @@ public Server(@Name("port") int port)
ServerConnector connector = new ServerConnector(this);
connector.setPort(port);
addConnector(connector);
- addBean(_attributes);
+ installBean(_attributes);
}
/**
@@ -142,13 +142,13 @@ public Server(@Name("threadPool") ThreadPool pool)
public Server(@Name("threadPool") ThreadPool threadPool, @Name("scheduler") Scheduler scheduler, @Name("bufferPool") ByteBufferPool bufferPool)
{
_threadPool = threadPool != null ? threadPool : new QueuedThreadPool();
- addBean(_threadPool);
+ installBean(_threadPool);
_scheduler = scheduler != null ? scheduler : new ScheduledExecutorScheduler();
- addBean(_scheduler);
+ installBean(_scheduler);
_bufferPool = bufferPool != null ? bufferPool : new ArrayByteBufferPool();
- addBean(_bufferPool);
+ installBean(_bufferPool);
setServer(this);
- addBean(FileSystemPool.INSTANCE, false);
+ installBean(FileSystemPool.INSTANCE, false);
}
public Handler getDefaultHandler()
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
index d5b8832e2b34..838c8af60620 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java
@@ -211,7 +211,7 @@ public ServerConnector(
{
super(server, executor, scheduler, bufferPool, acceptors, factories);
_manager = newSelectorManager(getExecutor(), getScheduler(), selectors);
- addBean(_manager, true);
+ installBean(_manager, true);
setAcceptorPriorityDelta(-2);
}
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
index aa894af0c3a5..e757fdd1b25f 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SslConnectionFactory.java
@@ -56,7 +56,7 @@ public SslConnectionFactory(@Name("sslContextFactory") SslContextFactory.Server
super("SSL");
_sslContextFactory = factory == null ? new SslContextFactory.Server() : factory;
_nextProtocol = nextProtocol;
- addBean(_sslContextFactory);
+ installBean(_sslContextFactory);
}
public SslContextFactory.Server getSslContextFactory()
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SymlinkAllowedResourceAliasChecker.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SymlinkAllowedResourceAliasChecker.java
index 6460a0e766fb..f629dc41beb3 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SymlinkAllowedResourceAliasChecker.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/SymlinkAllowedResourceAliasChecker.java
@@ -67,10 +67,11 @@ protected boolean check(String pathInContext, Path path)
// Add the segment to the path and realURI.
segmentPath.append("/").append(segment);
Resource fromBase = _baseResource.resolve(segmentPath.toString());
- for (Resource r : fromBase)
- {
- Path p = r.getPath();
+ // If there is a single path, check it
+ Path p = fromBase.getPath();
+ if (p != null)
+ {
// If the ancestor of the alias is a symlink, then check if the real URI is protected, otherwise allow.
// This allows symlinks like /other->/WEB-INF and /external->/var/lib/docroot
// This does not allow symlinks like /WeB-InF->/var/lib/other
@@ -80,10 +81,28 @@ protected boolean check(String pathInContext, Path path)
// If the ancestor is not allowed then do not allow.
if (!isAllowed(p))
return false;
+ }
+ else
+ {
+ // otherwise check all possibles
+ for (Resource r : fromBase)
+ {
+ p = r.getPath();
+
+ // If the ancestor of the alias is a symlink, then check if the real URI is protected, otherwise allow.
+ // This allows symlinks like /other->/WEB-INF and /external->/var/lib/docroot
+ // This does not allow symlinks like /WeB-InF->/var/lib/other
+ if (Files.isSymbolicLink(p))
+ return !getContextHandler().isProtectedTarget(segmentPath.toString());
- // TODO as we are building the realURI of the resource, it would be possible to
- // re-check that against security constraints.
+ // If the ancestor is not allowed then do not allow.
+ if (!isAllowed(p))
+ return false;
+ }
}
+
+ // TODO as we are building the realURI of the resource, it would be possible to
+ // re-check that against security constraints.
}
}
catch (Throwable t)
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 629afb82b597..c53a65f6f919 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
@@ -388,8 +388,6 @@ public void removeVirtualHosts(String... virtualHosts)
@ManagedAttribute(value = "Virtual hosts accepted by the context", readonly = true)
public List getVirtualHosts()
{
- if (_vhosts == null)
- return null;
return _vhosts.stream().map(VHost::getName).collect(Collectors.toList());
}
@@ -1044,7 +1042,7 @@ public String toString()
b.append(",b=").append(getBaseResource());
b.append(",a=").append(_availability);
- if (vhosts != null && !vhosts.isEmpty())
+ if (!vhosts.isEmpty())
{
b.append(",vh=[");
b.append(String.join(",", vhosts));
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
index caad1fb06392..896eea30e7ca 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
@@ -74,6 +74,7 @@ public class ErrorHandler implements Request.Handler
boolean _showStacks = true;
boolean _showMessageInTitle = true;
+ String _defaultResponseMimeType = Type.TEXT_HTML.asString();
HttpField _cacheControl = new PreEncodedHttpField(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store");
public ErrorHandler()
@@ -127,7 +128,7 @@ protected void generateResponse(Request request, Response response, int code, St
callback.succeeded();
return;
}
- acceptable = Collections.singletonList(Type.TEXT_HTML.asString());
+ acceptable = Collections.singletonList(_defaultResponseMimeType);
}
List charsets = request.getHeaders().getQualityCSV(HttpHeader.ACCEPT_CHARSET).stream()
.map(s ->
@@ -477,6 +478,23 @@ public void setShowMessageInTitle(boolean showMessageInTitle)
_showMessageInTitle = showMessageInTitle;
}
+ /**
+ * @return The mime type to be used when a client does not specify an Accept header, or the request did not fully parse
+ */
+ @ManagedAttribute("Mime type to be used when a client does not specify an Accept header, or the request did not fully parse")
+ public String getDefaultResponseMimeType()
+ {
+ return _defaultResponseMimeType;
+ }
+
+ /**
+ * @param defaultResponseMimeType The mime type to be used when a client does not specify an Accept header, or the request did not fully parse
+ */
+ public void setDefaultResponseMimeType(String defaultResponseMimeType)
+ {
+ _defaultResponseMimeType = Objects.requireNonNull(defaultResponseMimeType);;
+ }
+
protected void write(Writer writer, String string) throws IOException
{
if (string == null)
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/EventsHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/EventsHandler.java
index 4ff0547a4ea7..b338f5cb3bb6 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/EventsHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/EventsHandler.java
@@ -75,7 +75,7 @@ public boolean handle(Request request, Response response, Callback callback) thr
{
notifyOnResponseBegin(roRequest, wrappedResponse);
notifyOnResponseTrailersComplete(roRequest, wrappedResponse);
- notifyOnComplete(roRequest, x);
+ notifyOnComplete(roRequest, wrappedResponse, x);
});
boolean handled = super.handle(wrappedRequest, wrappedResponse, callback);
@@ -179,11 +179,11 @@ private void notifyOnResponseTrailersComplete(Request request, EventsResponse re
}
}
- private void notifyOnComplete(Request request, Throwable failure)
+ private void notifyOnComplete(Request request, Response response, Throwable failure)
{
try
{
- onComplete(request, failure);
+ onComplete(request, response.getStatus(), response.getHeaders().asImmutable(), failure);
}
catch (Throwable x)
{
@@ -308,12 +308,31 @@ protected void onResponseTrailersComplete(Request request, HttpFields trailers)
* has been completed).
*
* @param request the request object. The {@code read()}, {@code demand(Runnable)} and {@code fail(Throwable)} methods must not be called by the listener.
- * @param failure if there was a failure to complete
+ * @param failure if there was a failure to complete.
+ * @deprecated Override {@link #onComplete(Request, int, HttpFields, Throwable)} instead.
*/
+ @Deprecated
protected void onComplete(Request request, Throwable failure)
+ {
+ }
+
+ /**
+ * Invoked when the request and response processing are complete,
+ * just before the request and response will be recycled (i.e. after the
+ * {@link Runnable} return from {@link org.eclipse.jetty.server.HttpChannel#onRequest(MetaData.Request)}
+ * has returned and the {@link Callback} passed to {@link Handler#handle(Request, Response, Callback)}
+ * has been completed).
+ *
+ * @param request the request object. The {@code read()}, {@code demand(Runnable)} and {@code fail(Throwable)} methods must not be called by the listener.
+ * @param status the response status.
+ * @param headers the immutable fields of the response object.
+ * @param failure if there was a failure to complete.
+ */
+ protected void onComplete(Request request, int status, HttpFields headers, Throwable failure)
{
if (LOG.isDebugEnabled())
- LOG.debug("onComplete of {}", request, failure);
+ LOG.debug("onComplete of {} status={} headers={}", request, status, headers);
+ onComplete(request, failure);
}
private class EventsResponse extends Response.Wrapper
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/LatencyRecordingHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/LatencyRecordingHandler.java
index 908bcb65188d..d3f11adc0ac7 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/LatencyRecordingHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/LatencyRecordingHandler.java
@@ -13,6 +13,7 @@
package org.eclipse.jetty.server.handler;
+import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.NanoTime;
@@ -35,7 +36,7 @@ public LatencyRecordingHandler(Handler handler)
}
@Override
- protected final void onComplete(Request request, Throwable failure)
+ protected final void onComplete(Request request, int status, HttpFields headers, Throwable failure)
{
onRequestComplete(request.getId(), NanoTime.since(request.getBeginNanoTime()));
}
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
index 13bd9622d174..4d6573e4b09e 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java
@@ -89,19 +89,6 @@ protected void onRequestRead(Request request, Content.Chunk chunk)
_bytesRead.add(chunk.remaining());
}
- @Override
- protected void onResponseBegin(Request request, int status, HttpFields headers)
- {
- switch (status / 100)
- {
- case 1 -> _responses1xx.increment();
- case 2 -> _responses2xx.increment();
- case 3 -> _responses3xx.increment();
- case 4 -> _responses4xx.increment();
- case 5 -> _responses5xx.increment();
- }
- }
-
@Override
protected void onResponseWrite(Request request, boolean last, ByteBuffer content)
{
@@ -111,12 +98,20 @@ protected void onResponseWrite(Request request, boolean last, ByteBuffer content
}
@Override
- protected void onComplete(Request request, Throwable failure)
+ protected void onComplete(Request request, int status, HttpFields headers, Throwable failure)
{
if (failure != null)
_failures.increment();
_requestTimeStats.record(NanoTime.since(request.getBeginNanoTime()));
_requestStats.decrement();
+ switch (status / 100)
+ {
+ case 1 -> _responses1xx.increment();
+ case 2 -> _responses2xx.increment();
+ case 3 -> _responses3xx.increment();
+ case 4 -> _responses4xx.increment();
+ case 5 -> _responses5xx.increment();
+ }
}
@Override
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorHandlerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorHandlerTest.java
index 386aa48962f8..52eea5824262 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorHandlerTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/ErrorHandlerTest.java
@@ -26,6 +26,7 @@
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
+import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.QuietException;
import org.eclipse.jetty.logging.StacklessLogging;
@@ -159,6 +160,31 @@ public void test404NoAccept() throws Exception
assertContent(response);
}
+ @Test
+ public void test404NoAcceptButSpecifiedDefaultResponseMimeType() throws Exception
+ {
+ ErrorHandler errorHandler = new ErrorHandler();
+ errorHandler.setDefaultResponseMimeType(MimeTypes.Type.APPLICATION_JSON.asString());
+ server.setErrorHandler(errorHandler);
+
+ String rawResponse = connector.getResponse("""
+ GET / HTTP/1.1
+ Host: Localhost
+
+ """);
+
+ HttpTester.Response response = HttpTester.parseResponse(rawResponse);
+
+ assertThat("Response status code", response.getStatus(), is(404));
+ assertThat("Response Content-Length", response.getField(HttpHeader.CONTENT_LENGTH).getIntValue(), greaterThan(0));
+ assertThat("Response Content-Type", response.get(HttpHeader.CONTENT_TYPE), containsString("application/json"));
+ assertThat(response.get(HttpHeader.DATE), notNullValue());
+ assertThat(response.getContent(), containsString("\"status\":\"404\""));
+ assertThat(response.getContent(), containsString("\"message\":\"Not Found\""));
+
+ assertContent(response);
+ }
+
@Test
public void test404EmptyAccept() throws Exception
{
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/EventsHandlerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/EventsHandlerTest.java
index 7238184b05f9..f61ef2818385 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/EventsHandlerTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/EventsHandlerTest.java
@@ -13,14 +13,21 @@
package org.eclipse.jetty.server.handler;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -29,6 +36,7 @@
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
@@ -84,7 +92,7 @@ protected void onResponseBegin(Request request, int status, HttpFields headers)
}
@Override
- protected void onComplete(Request request, Throwable failure)
+ protected void onComplete(Request request, int status, HttpFields headers, Throwable failure)
{
attribute.set((String)request.getAttribute(ATTRIBUTE_NAME));
}
@@ -112,7 +120,7 @@ public void testNanoTimestamps() throws Exception
EventsHandler eventsHandler = new EventsHandler(new EchoHandler())
{
@Override
- protected void onComplete(Request request, Throwable failure)
+ protected void onComplete(Request request, int status, HttpFields headers, Throwable failure)
{
beginNanoTime.set(request.getBeginNanoTime());
readyNanoTime.set(request.getHeadersNanoTime());
@@ -144,4 +152,82 @@ protected void onComplete(Request request, Throwable failure)
assertThat(NanoTime.millisSince(readyNanoTime.get()), greaterThan(450L));
}
}
+
+ @Test
+ public void testEventsOfNoopHandler() throws Exception
+ {
+ List events = new CopyOnWriteArrayList<>();
+
+ EventsHandler eventsHandler = new EventsHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ callback.succeeded();
+ return true;
+ }
+ })
+ {
+ @Override
+ protected void onRequestRead(Request request, Content.Chunk chunk)
+ {
+ events.add("onRequestRead");
+ }
+
+ @Override
+ protected void onResponseWrite(Request request, boolean last, ByteBuffer content)
+ {
+ events.add("onResponseWrite");
+ }
+
+ @Override
+ protected void onResponseWriteComplete(Request request, Throwable failure)
+ {
+ events.add("onResponseWriteComplete");
+ }
+
+ @Override
+ protected void onResponseTrailersComplete(Request request, HttpFields trailers)
+ {
+ events.add("onResponseTrailersComplete");
+ }
+
+ @Override
+ protected void onBeforeHandling(Request request)
+ {
+ events.add("onBeforeHandling");
+ }
+
+ @Override
+ protected void onAfterHandling(Request request, boolean handled, Throwable failure)
+ {
+ events.add("onAfterHandling");
+ }
+
+ @Override
+ protected void onResponseBegin(Request request, int status, HttpFields headers)
+ {
+ events.add("onResponseBegin");
+ }
+
+ @Override
+ protected void onComplete(Request request, int status, HttpFields headers, Throwable failure)
+ {
+ events.add("onComplete");
+ }
+ };
+
+ startServer(eventsHandler);
+
+ String rawRequest = """
+ GET / HTTP/1.1\r
+ Host: localhost\r
+ Connection: close\r
+ \r
+ """;
+
+ String response = connector.getResponse(rawRequest);
+ assertThat(response, containsString("HTTP/1.1 200 OK"));
+ assertThat(events, equalTo(Arrays.asList("onBeforeHandling", "onAfterHandling", "onResponseBegin", "onComplete")));
+ }
}
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
index 1a1a2a477562..8bcd7d440a02 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/StatisticsHandlerTest.java
@@ -15,6 +15,7 @@
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
@@ -47,6 +48,7 @@
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -307,6 +309,87 @@ public void testTwoRequestsSerially() throws Exception
assertEquals(0, _statsHandler.getFailures());
}
+ @Test
+ public void testIncompleteContentRecordsStatus200() throws Exception
+ {
+ _statsHandler.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ callback.succeeded();
+ return true;
+ }
+ });
+ _server.start();
+
+ String request = """
+ POST / HTTP/1.1
+ Host: localhost
+ Transfer-Encoding: chunked
+ Connection: close
+
+ 0a
+ 0123456789
+ """;
+
+ String response = _connector.getResponse(request);
+ assertThat(response, containsString("HTTP/1.1 200 OK"));
+ assertThat(response, containsString("Connection: close"));
+ await().atMost(5, TimeUnit.SECONDS).until(_statsHandler::getResponses2xx, is(1));
+ }
+
+ @Test
+ public void testInvalidContentLengthRecordsStatus500() throws Exception
+ {
+ _statsHandler.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ response.getHeaders().put("content-length", 1L);
+ callback.succeeded();
+ return true;
+ }
+ });
+ _server.start();
+
+ String request = """
+ GET / HTTP/1.1\r
+ Host: localhost\r
+ \r
+ """;
+
+ String response = _connector.getResponse(request);
+ assertThat(response, is(nullValue()));
+ await().atMost(5, TimeUnit.SECONDS).until(_statsHandler::getResponses5xx, is(1));
+ }
+
+ @Test
+ public void testImplicitStatus200Recorded() throws Exception
+ {
+ _statsHandler.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback)
+ {
+ // Do not explicitly set status to 200.
+ response.write(true, ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8)), callback);
+ return true;
+ }
+ });
+ _server.start();
+
+ String request = """
+ GET / HTTP/1.1\r
+ Host: localhost\r
+ \r
+ """;
+ String response = _connector.getResponse(request);
+ assertThat(response, containsString("HTTP/1.1 200 OK"));
+ await().atMost(5, TimeUnit.SECONDS).until(_statsHandler::getResponses2xx, is(1));
+ }
+
@Test
public void testTwoRequestsInParallel() throws Exception
{
diff --git a/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/CachingSessionDataStore.java b/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/CachingSessionDataStore.java
index 7941f25efb17..5e9c6e98965b 100644
--- a/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/CachingSessionDataStore.java
+++ b/jetty-core/jetty-session/src/main/java/org/eclipse/jetty/session/CachingSessionDataStore.java
@@ -52,14 +52,14 @@ public class CachingSessionDataStore extends ContainerLifeCycle implements Sessi
/**
* @param cache the front cache to use
- * @param store the actual store for the the session data
+ * @param store the actual store for the session data
*/
public CachingSessionDataStore(SessionDataMap cache, SessionDataStore store)
{
_cache = cache;
- addBean(_cache, true);
+ installBean(_cache, true);
_store = store;
- addBean(_store, true);
+ installBean(_store, true);
}
/**
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EventsHandlerTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EventsHandlerTest.java
index 026fa5702e80..6fa6d887243e 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EventsHandlerTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/EventsHandlerTest.java
@@ -372,7 +372,7 @@ protected void onResponseWriteComplete(Request request, Throwable failure)
}
@Override
- protected void onComplete(Request request, Throwable failure)
+ protected void onComplete(Request request, int status, HttpFields headers, Throwable failure)
{
addEvent("onComplete");
}
@@ -441,7 +441,7 @@ protected void onResponseTrailersComplete(Request request, HttpFields trailers)
}
@Override
- protected void onComplete(Request request, Throwable failure)
+ protected void onComplete(Request request, int status, HttpFields headers, Throwable failure)
{
// System.out.println("onComplete");
useForbiddenMethods(request, exceptions);
diff --git a/jetty-core/jetty-unixdomain-server/src/main/java/org/eclipse/jetty/unixdomain/server/UnixDomainServerConnector.java b/jetty-core/jetty-unixdomain-server/src/main/java/org/eclipse/jetty/unixdomain/server/UnixDomainServerConnector.java
index 0629b66e4fba..5681314f03c3 100644
--- a/jetty-core/jetty-unixdomain-server/src/main/java/org/eclipse/jetty/unixdomain/server/UnixDomainServerConnector.java
+++ b/jetty-core/jetty-unixdomain-server/src/main/java/org/eclipse/jetty/unixdomain/server/UnixDomainServerConnector.java
@@ -82,7 +82,7 @@ public UnixDomainServerConnector(Server server, Executor executor, Scheduler sch
{
super(server, executor, scheduler, bufferPool, acceptors, factories.length > 0 ? factories : new ConnectionFactory[]{new HttpConnectionFactory()});
selectorManager = newSelectorManager(getExecutor(), getScheduler(), selectors);
- addBean(selectorManager, true);
+ installBean(selectorManager, true);
}
protected SelectorManager newSelectorManager(Executor executor, Scheduler scheduler, int selectors)
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
index 22cae03b0d2c..c96a75f6c236 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
@@ -265,7 +265,7 @@ private int startIndex(int size)
case FIRST -> 0;
case RANDOM -> ThreadLocalRandom.current().nextInt(size);
case ROUND_ROBIN -> nextIndex.getAndUpdate(c -> Math.max(0, c + 1)) % size;
- case THREAD_ID -> (int)((Thread.currentThread().getId() * 31) % size);
+ case THREAD_ID -> (int)(Thread.currentThread().getId() % size);
};
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java
index d65aa3d37529..d98fdb82ea74 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java
@@ -81,6 +81,8 @@ private static String formatTimestamp(String timestamp)
{
try
{
+ if (StringUtil.isBlank(timestamp))
+ return "unknown";
long epochMillis = Long.parseLong(timestamp);
return Instant.ofEpochMilli(epochMillis).toString();
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java
index 4b53a983c7f5..09c12e0dc931 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java
@@ -52,4 +52,11 @@ public static int getLongsPerCacheLine()
{
return getCacheLineBytes() >> 3;
}
+
+ public static int getReferencesPerCacheLine()
+ {
+ // Use getIntegersPerCacheLine() instead of getLongsPerCacheLine() b/c refs (oops) could be
+ // compressed down to 32-bit; maybe this could be detected?
+ return getIntegersPerCacheLine();
+ }
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java
index 4409d113e120..66134183b066 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java
@@ -371,7 +371,7 @@ public Scanner(Scheduler scheduler, boolean reportRealPaths)
{
//Create the scheduler and start it
_scheduler = scheduler == null ? new ScheduledExecutorScheduler("Scanner-" + SCANNER_IDS.getAndIncrement(), true, 1) : scheduler;
- addBean(_scheduler);
+ installBean(_scheduler);
_linkOptions = reportRealPaths ? new LinkOption[0] : new LinkOption[] {LinkOption.NOFOLLOW_LINKS};
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
index a2ae71be17ce..8b9510164d7c 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
@@ -803,4 +803,17 @@ private TypeUtil()
{
// prevents instantiation
}
+
+ /**
+ * Get the next highest power of two
+ * @param value An integer
+ * @return a power of two that is greater than or equal to {@code value}
+ */
+ public static int ceilToNextPowerOfTwo(int value)
+ {
+ if (value < 0)
+ throw new IllegalArgumentException("value must not be negative");
+ int result = 1 << (Integer.SIZE - Integer.numberOfLeadingZeros(value - 1));
+ return result > 0 ? result : Integer.MAX_VALUE;
+ }
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
index 2b8f639b271a..0e4a0ae37bcd 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java
@@ -300,6 +300,7 @@ public boolean isUnmanaged(Object bean)
* The {@link #addBean(Object, boolean)}
* method should be used if this is not correct, or the {@link #manage(Object)} and {@link #unmanage(Object)}
* methods may be used after an add to change the status.
+ *
This method should not be called from a constructor, instead use {@link #installBean(Object)}
*
* @param o the bean object to add
* @return true if the bean was added, false if it was already present
@@ -309,12 +310,12 @@ public boolean addBean(Object o)
{
if (o instanceof LifeCycle l)
return addBean(o, l.isRunning() ? Managed.UNMANAGED : Managed.AUTO);
-
return addBean(o, Managed.POJO);
}
/**
- * Adds the given bean, explicitly managing it or not.
+ *
Adds the given bean, explicitly managing it or not.
+ *
This method should not be called from a constructor, instead use {@link #installBean(Object)}
*
* @param o The bean object to add
* @param managed whether to manage the lifecycle of the bean
@@ -413,6 +414,56 @@ else if (isStarted())
return true;
}
+ /**
+ * Add a bean in a way that is safe to call from a super constructor of this {@code ContainerLifeCycle}:
+ * there are no {@link Container.Listener}s registered;
+ * the object itself is not a {@link Container.Listener};
+ * this {@link LifeCycle} is not started or starting;
+ * and the is no debugging call to {@code this.toString()}.
+ * @param o The bean to add
+ * @return true if the bean was added
+ */
+ protected final boolean installBean(Object o)
+ {
+ return installBean(o, o instanceof LifeCycle l ? (l.isRunning() ? Managed.UNMANAGED : Managed.AUTO) : Managed.POJO);
+ }
+
+ /**
+ * Add a bean in a way that is safe to call from a super constructor of this {@code ContainerLifeCycle}:
+ * there are no {@link Container.Listener}s registered;
+ * the object itself is not a {@link Container.Listener};
+ * this {@link LifeCycle} is not started or starting;
+ * and the is no debugging call to {@code this.toString()}.
+ * @param o The bean to add
+ * @param managed true if the bean is to be managed.
+ * @return true if the bean was added
+ */
+ protected final boolean installBean(Object o, boolean managed)
+ {
+ return installBean(o, o instanceof LifeCycle
+ ? (managed ? Managed.MANAGED : Managed.UNMANAGED)
+ : (managed ? Managed.POJO : Managed.UNMANAGED));
+ }
+
+ private boolean installBean(Object o, Managed managed)
+ {
+ if (o == null || contains(o))
+ return false;
+ if (o instanceof Container.Listener || !_listeners.isEmpty())
+ throw new IllegalArgumentException("Cannot call Container.Listeners from constructor");
+
+ if (o instanceof EventListener eventListener)
+ addEventListener(eventListener);
+
+ Bean newBean = new Bean(o);
+ newBean._managed = managed;
+ _beans.add(newBean);
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("{}@{} added {}", getClass().getSimpleName(), hashCode(), newBean);
+ return true;
+ }
+
/**
* Adds a managed lifecycle.
*
This is a convenience method that uses addBean(lifecycle,true)
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AttributeNormalizer.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AttributeNormalizer.java
index b5a956037d47..afa0f7da6ae2 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AttributeNormalizer.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/AttributeNormalizer.java
@@ -29,7 +29,6 @@
import java.util.stream.Stream;
import org.eclipse.jetty.util.StringUtil;
-import org.eclipse.jetty.util.URIUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -411,17 +410,11 @@ private String expand(String prefix, String property, String suffix)
{
case "WAR", "WAR.path" ->
{
- Resource r = baseResource.resolve(suffix);
- if (r == null)
- return prefix + URIUtil.addPaths(baseResource.iterator().next().getPath().toString(), suffix);
- return prefix + r.getPath();
+ return prefix + baseResource.resolve(suffix).getPath();
}
case "WAR.uri" ->
{
- Resource r = baseResource.resolve(suffix);
- if (r == null)
- return prefix + URIUtil.addPaths(baseResource.iterator().next().getURI().toString(), suffix);
- return prefix + r.getURI();
+ return prefix + baseResource.resolve(suffix).getURI();
}
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/CombinedResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/CombinedResource.java
index 516968a9699b..7d22ed2db04b 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/CombinedResource.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/CombinedResource.java
@@ -153,20 +153,23 @@ public Resource resolve(String subUriPath)
// Attempt a simple (single) Resource lookup that exists
Resource resolved = null;
+ Resource notFound = null;
for (Resource res : _resources)
{
resolved = res.resolve(subUriPath);
- if (Resources.missing(resolved))
- continue; // skip, doesn't exist
- if (!resolved.isDirectory())
+ if (!Resources.missing(resolved) && !resolved.isDirectory())
return resolved; // Return simple (non-directory) Resource
+
+ if (Resources.missing(resolved) && notFound == null)
+ notFound = resolved;
+
if (resources == null)
resources = new ArrayList<>();
resources.add(resolved);
}
if (resources == null)
- return resolved; // This will not exist
+ return notFound; // This will not exist
if (resources.size() == 1)
return resources.get(0);
@@ -177,13 +180,28 @@ public Resource resolve(String subUriPath)
@Override
public boolean exists()
{
- return _resources.stream().anyMatch(Resource::exists);
+ for (Resource r : _resources)
+ if (r.exists())
+ return true;
+ return false;
}
@Override
public Path getPath()
{
- return null;
+ int exists = 0;
+ Path path = null;
+ for (Resource r : _resources)
+ {
+ if (r.exists() && exists++ == 0)
+ path = r.getPath();
+ }
+ return switch (exists)
+ {
+ case 0 -> _resources.get(0).getPath();
+ case 1 -> path;
+ default -> null;
+ };
}
@Override
@@ -213,7 +231,19 @@ else if (!filename.equals(fn))
@Override
public URI getURI()
{
- return null;
+ int exists = 0;
+ URI uri = null;
+ for (Resource r : _resources)
+ {
+ if (r.exists() && exists++ == 0)
+ uri = r.getURI();
+ }
+ return switch (exists)
+ {
+ case 0 -> _resources.get(0).getURI();
+ case 1 -> uri;
+ default -> null;
+ };
}
@Override
@@ -289,6 +319,8 @@ public void copyTo(Path destination) throws IOException
Collection all = getAllResources();
for (Resource r : all)
{
+ if (!r.exists())
+ continue;
Path relative = getPathTo(r);
Path pathTo = Objects.equals(relative.getFileSystem(), destination.getFileSystem())
? destination.resolve(relative)
@@ -335,6 +367,37 @@ public int hashCode()
return Objects.hash(_resources);
}
+ @Override
+ public boolean isAlias()
+ {
+ for (Resource r : _resources)
+ {
+ if (r.isAlias())
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public URI getRealURI()
+ {
+ if (!isAlias())
+ return getURI();
+ int exists = 0;
+ URI uri = null;
+ for (Resource r : _resources)
+ {
+ if (r.exists() && exists++ == 0)
+ uri = r.getRealURI();
+ }
+ return switch (exists)
+ {
+ case 0 -> _resources.get(0).getRealURI();
+ case 1 -> uri;
+ default -> null;
+ };
+ }
+
/**
* @return the list of resources
*/
@@ -375,6 +438,8 @@ public Path getPathTo(Resource other)
// return true it's relative location to the first matching resource.
for (Resource r : _resources)
{
+ if (!r.exists())
+ continue;
Path path = r.getPath();
if (otherPath.startsWith(path))
return path.relativize(otherPath);
@@ -387,8 +452,14 @@ public Path getPathTo(Resource other)
Path relative = null;
loop : for (Resource o : other)
{
+ if (!o.exists())
+ continue;
+
for (Resource r : _resources)
{
+ if (!r.exists())
+ continue;
+
if (o.getPath().startsWith(r.getPath()))
{
Path rel = r.getPath().relativize(o.getPath());
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java
index 14013f9d557f..b095f922e498 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java
@@ -292,10 +292,7 @@ public Resource resolve(String subUriPath)
URI uri = getURI();
URI resolvedUri = URIUtil.addPath(uri, subUriPath);
Path path = Paths.get(resolvedUri);
- if (Files.exists(path))
- return newResource(path, resolvedUri);
-
- return null;
+ return newResource(path, resolvedUri);
}
/**
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java
index 2c739ca225e2..c86d2b10a6be 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResourceFactory.java
@@ -14,8 +14,6 @@
package org.eclipse.jetty.util.resource;
import java.net.URI;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.nio.file.Paths;
public class PathResourceFactory implements ResourceFactory
@@ -23,9 +21,6 @@ public class PathResourceFactory implements ResourceFactory
@Override
public Resource newResource(URI uri)
{
- Path path = Paths.get(uri.normalize());
- if (!Files.exists(path))
- return null;
- return new PathResource(path, uri, false);
+ return new PathResource(Paths.get(uri.normalize()), uri, false);
}
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
index 37c417a001ef..ec0dce3ed146 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java
@@ -268,7 +268,8 @@ public List list()
* Resolve an existing Resource.
*
* @param subUriPath the encoded subUriPath
- * @return an existing Resource representing the requested subUriPath, or null if resource does not exist.
+ * @return a Resource representing the requested subUriPath, which may not {@link #exists() exist},
+ * or null if the resource cannot exist.
* @throws IllegalArgumentException if subUriPath is invalid
*/
public abstract Resource resolve(String subUriPath);
@@ -303,11 +304,17 @@ public URI getRealURI()
public void copyTo(Path destination)
throws IOException
{
+ if (!exists())
+ throw new IOException("Resource does not exist: " + getFileName());
+
Path src = getPath();
if (src == null)
{
if (!isDirectory())
{
+ if (Files.isDirectory(destination))
+ destination = destination.resolve(getFileName());
+
// use old school stream based copy
try (InputStream in = newInputStream(); OutputStream out = Files.newOutputStream(destination))
{
@@ -327,7 +334,6 @@ public void copyTo(Path destination)
// to a directory, preserve the filename
Path destPath = destination.resolve(src.getFileName().toString());
Files.copy(src, destPath,
- StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.COPY_ATTRIBUTES,
StandardCopyOption.REPLACE_EXISTING);
}
@@ -335,7 +341,6 @@ public void copyTo(Path destination)
{
// to a file, use destination as-is
Files.copy(src, destination,
- StandardCopyOption.ATOMIC_MOVE,
StandardCopyOption.COPY_ATTRIBUTES,
StandardCopyOption.REPLACE_EXISTING);
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/KeyStoreScanner.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/KeyStoreScanner.java
index 1be738b27889..6f823479d873 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/KeyStoreScanner.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/KeyStoreScanner.java
@@ -68,7 +68,7 @@ public KeyStoreScanner(SslContextFactory sslContextFactory)
_scanner.setReportExistingFilesOnStartup(false);
_scanner.setScanDepth(1);
_scanner.addListener(this);
- addBean(_scanner);
+ installBean(_scanner);
}
private Path getRealKeyStorePath()
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/MonitoredQueuedThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/MonitoredQueuedThreadPool.java
index fdc39f85b1b2..275608eb6339 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/MonitoredQueuedThreadPool.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/MonitoredQueuedThreadPool.java
@@ -47,10 +47,10 @@ public MonitoredQueuedThreadPool(int maxThreads)
public MonitoredQueuedThreadPool(int maxThreads, int minThreads, int idleTimeOut, BlockingQueue queue)
{
super(maxThreads, minThreads, idleTimeOut, queue);
- addBean(queueStats);
- addBean(queueLatencyStats);
- addBean(taskLatencyStats);
- addBean(threadStats);
+ installBean(queueStats);
+ installBean(queueLatencyStats);
+ installBean(taskLatencyStats);
+ installBean(threadStats);
}
@Override
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
index c4f5d191c818..2078d92609ea 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java
@@ -213,9 +213,17 @@ protected void doStart() throws Exception
}
else
{
- ReservedThreadExecutor reserved = new ReservedThreadExecutor(this, _reservedThreads);
- reserved.setIdleTimeout(_idleTimeout, TimeUnit.MILLISECONDS);
- _tryExecutor = reserved;
+ int reserved = ReservedThreadExecutor.reservedThreads(this, _reservedThreads);
+ if (reserved == 0)
+ {
+ _tryExecutor = NO_TRY;
+ }
+ else
+ {
+ ReservedThreadExecutor rte = new ReservedThreadExecutor(this, _reservedThreads);
+ rte.setIdleTimeout(_idleTimeout, TimeUnit.MILLISECONDS);
+ _tryExecutor = rte;
+ }
}
addBean(_tryExecutor);
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java
index 3f23becf49ac..8a0cbeddb8a7 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java
@@ -14,32 +14,22 @@
package org.eclipse.jetty.util.thread;
import java.io.IOException;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Collectors;
+import java.util.concurrent.atomic.AtomicInteger;
-import org.eclipse.jetty.util.AtomicBiInteger;
-import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.ProcessorUtils;
+import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.VirtualThreads;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
-import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
-import org.eclipse.jetty.util.component.DumpableCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import static java.util.concurrent.TimeUnit.NANOSECONDS;
-import static org.eclipse.jetty.util.AtomicBiInteger.getHi;
-import static org.eclipse.jetty.util.AtomicBiInteger.getLo;
-
/**
*
A TryExecutor using pre-allocated/reserved threads from an external Executor.
*
Calls to {@link #tryExecute(Runnable)} on ReservedThreadExecutor will either
@@ -51,48 +41,68 @@
* the external Executor.
*/
@ManagedObject("A pool for reserved threads")
-public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExecutor, Dumpable
+public class ReservedThreadExecutor extends ContainerLifeCycle implements TryExecutor, Dumpable
{
private static final Logger LOG = LoggerFactory.getLogger(ReservedThreadExecutor.class);
- private static final long DEFAULT_IDLE_TIMEOUT = TimeUnit.MINUTES.toNanos(1);
- private static final Runnable STOP = new Runnable()
- {
- @Override
- public void run()
- {
- }
-
- @Override
- public String toString()
- {
- return "STOP";
- }
- };
private final Executor _executor;
- private final int _capacity;
- private final Set _threads = ConcurrentHashMap.newKeySet();
- private final SynchronousQueue _queue = new SynchronousQueue<>(false);
- private final AtomicBiInteger _count = new AtomicBiInteger(); // hi=pending; lo=size;
- private final AtomicLong _lastEmptyNanoTime = new AtomicLong(NanoTime.now());
+ private final ThreadIdPool _threads;
+ private final AtomicInteger _pending = new AtomicInteger();
+ private final int _minSize;
+ private final int _maxPending;
private ThreadPoolBudget.Lease _lease;
- private long _idleTimeNanos = DEFAULT_IDLE_TIMEOUT;
+ private long _idleTimeoutMs;
/**
* @param executor The executor to use to obtain threads
- * @param capacity The number of threads to preallocate. If less than 0 then capacity
- * is calculated based on a heuristic from the number of available processors and
- * thread pool size.
+ * @param capacity The number of threads that can be reserved. If less than 0 then capacity
+ * is calculated based on a heuristic from the number of available processors and
+ * thread pool type.
*/
public ReservedThreadExecutor(Executor executor, int capacity)
+ {
+ this(executor, capacity, -1);
+ }
+
+ /**
+ * @param executor The executor to use to obtain threads
+ * @param capacity The number of threads that can be reserved. If less than 0 then capacity
+ * is calculated based on a heuristic from the number of available processors and
+ * thread pool type.
+ * @param minSize The minimum number of reserve Threads that the algorithm tries to maintain, or -1 for a heuristic value.
+ */
+ public ReservedThreadExecutor(Executor executor, int capacity, int minSize)
+ {
+ this(executor, capacity, minSize, -1);
+ }
+
+ /**
+ * @param executor The executor to use to obtain threads
+ * @param capacity The number of threads that can be reserved. If less than 0 then capacity
+ * is calculated based on a heuristic from the number of available processors and
+ * thread pool type.
+ * @param minSize The minimum number of reserve Threads that the algorithm tries to maintain, or -1 for a heuristic value.
+ * @param maxPending The maximum number of reserved Threads to start, or -1 for no limit.
+ */
+ public ReservedThreadExecutor(Executor executor, int capacity, int minSize, int maxPending)
{
_executor = executor;
- _capacity = reservedThreads(executor, capacity);
+ _threads = new ThreadIdPool<>(reservedThreads(executor, capacity));
+ _minSize = minSize < 0 ? Math.min(1, _threads.capacity()) : minSize;
+ if (_minSize > _threads.capacity())
+ throw new IllegalArgumentException("minSize larger than capacity");
+ _maxPending = maxPending;
+ if (_maxPending == 0)
+ throw new IllegalArgumentException("maxPending cannot be 0");
if (LOG.isDebugEnabled())
LOG.debug("{}", this);
+ installBean(_executor);
+ installBean(_threads);
}
/**
+ * Get the heuristic number of reserved threads.
+ *
* @param executor The executor to use to obtain threads
* @param capacity The number of threads to preallocate, If less than 0 then capacity
* is calculated based on a heuristic from the number of available processors and
@@ -100,7 +110,7 @@ public ReservedThreadExecutor(Executor executor, int capacity)
* @return the number of reserved threads that would be used by a ReservedThreadExecutor
* constructed with these arguments.
*/
- private static int reservedThreads(Executor executor, int capacity)
+ public static int reservedThreads(Executor executor, int capacity)
{
if (capacity >= 0)
return capacity;
@@ -110,7 +120,7 @@ private static int reservedThreads(Executor executor, int capacity)
if (executor instanceof ThreadPool.SizedThreadPool)
{
int threads = ((ThreadPool.SizedThreadPool)executor).getMaxThreads();
- return Math.max(1, Math.min(cpus, threads / 10));
+ return Math.max(1, TypeUtil.ceilToNextPowerOfTwo(Math.min(cpus, threads / 8)));
}
return cpus;
}
@@ -126,7 +136,7 @@ public Executor getExecutor()
@ManagedAttribute(value = "max number of reserved threads", readonly = true)
public int getCapacity()
{
- return _capacity;
+ return _threads.capacity();
}
/**
@@ -135,19 +145,20 @@ public int getCapacity()
@ManagedAttribute(value = "available reserved threads", readonly = true)
public int getAvailable()
{
- return _count.getLo();
+ return _threads.size();
}
- @ManagedAttribute(value = "pending reserved threads", readonly = true)
+ @ManagedAttribute(value = "pending reserved threads (deprecated)", readonly = true)
+ @Deprecated
public int getPending()
{
- return _count.getHi();
+ return 0;
}
@ManagedAttribute(value = "idle timeout in ms", readonly = true)
public long getIdleTimeoutMs()
{
- return NANOSECONDS.toMillis(_idleTimeNanos);
+ return _idleTimeoutMs;
}
/**
@@ -160,14 +171,13 @@ public void setIdleTimeout(long idleTime, TimeUnit idleTimeUnit)
{
if (isRunning())
throw new IllegalStateException();
- _idleTimeNanos = (idleTime <= 0 || idleTimeUnit == null) ? DEFAULT_IDLE_TIMEOUT : idleTimeUnit.toNanos(idleTime);
+ _idleTimeoutMs = idleTime <= 0 ? 0 : idleTimeUnit.toMillis(idleTime);
}
@Override
public void doStart() throws Exception
{
- _lease = ThreadPoolBudget.leaseFrom(getExecutor(), this, _capacity);
- _count.set(0, 0);
+ _lease = ThreadPoolBudget.leaseFrom(getExecutor(), this, getCapacity());
super.doStart();
}
@@ -179,27 +189,7 @@ public void doStop() throws Exception
super.doStop();
- // Mark this instance as stopped.
- int size = _count.getAndSetLo(-1);
-
- // Offer the STOP task to all waiting reserved threads.
- for (int i = 0; i < size; ++i)
- {
- // Yield to wait for any reserved threads that
- // have incremented the size but not yet polled.
- Thread.yield();
- _queue.offer(STOP);
- }
-
- // Interrupt any reserved thread missed the offer,
- // so they do not wait for the whole idle timeout.
- _threads.stream()
- .filter(ReservedThread::isReserved)
- .map(t -> t._thread)
- .filter(Objects::nonNull)
- .forEach(Thread::interrupt);
- _threads.clear();
- _count.getAndSetHi(0);
+ _threads.removeAll().forEach(ReservedThread::stop);
}
@Override
@@ -217,225 +207,181 @@ public void execute(Runnable task) throws RejectedExecutionException
@Override
public boolean tryExecute(Runnable task)
{
- if (LOG.isDebugEnabled())
- LOG.debug("{} tryExecute {}", this, task);
if (task == null)
return false;
- // Offer will only succeed if there is a reserved thread waiting
- boolean offered = _queue.offer(task);
-
- // If the offer succeeded we need to reduce the size, unless it is set to -1 in the meantime
- int size = _count.getLo();
- while (offered && size > 0 && !_count.compareAndSetLo(size, --size))
- size = _count.getLo();
+ ReservedThread reserved = _threads.take();
+ if (reserved != null)
+ {
+ reserved.wakeup(task);
+ return true;
+ }
- // If size is 0 and we are not stopping, start a new reserved thread
- if (size == 0 && task != STOP)
- startReservedThread();
+ startReservedThread();
- return offered;
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} tryExecute failed for {}", this, task);
+ return false;
}
private void startReservedThread()
{
- while (true)
+ if (_maxPending > 0 && _pending.incrementAndGet() >= _maxPending)
{
- long count = _count.get();
- int pending = getHi(count);
- int size = getLo(count);
- if (size < 0 || pending + size >= _capacity)
- return;
- if (size == 0)
- _lastEmptyNanoTime.set(NanoTime.now());
- if (!_count.compareAndSet(count, pending + 1, size))
- continue;
+ _pending.decrementAndGet();
+ return;
+ }
+ try
+ {
+ ReservedThread thread = new ReservedThread();
+ _executor.execute(thread);
+ }
+ catch (Throwable e)
+ {
if (LOG.isDebugEnabled())
- LOG.debug("{} startReservedThread p={}", this, pending + 1);
- try
- {
- ReservedThread thread = new ReservedThread();
- _threads.add(thread);
- _executor.execute(thread);
- }
- catch (Throwable e)
- {
- _count.add(-1, 0);
- if (LOG.isDebugEnabled())
- LOG.debug("ignored", e);
- }
- return;
+ LOG.debug("ignored", e);
}
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
- Dumpable.dumpObjects(out, indent, this,
- new DumpableCollection("threads",
- _threads.stream()
- .filter(ReservedThread::isReserved)
- .collect(Collectors.toList())));
+ Dumpable.dumpObjects(out, indent, this);
}
@Override
public String toString()
{
- return String.format("%s@%x{reserved=%d/%d,pending=%d}",
+ return String.format("%s@%x{capacity=%d,threads=%s}",
getClass().getSimpleName(),
hashCode(),
- _count.getLo(),
- _capacity,
- _count.getHi());
- }
-
- private enum State
- {
- PENDING,
- RESERVED,
- RUNNING,
- IDLE,
- STOPPED
+ getCapacity(),
+ _threads);
}
private class ReservedThread implements Runnable
{
- // The state and thread are kept only for dumping
- private volatile State _state = State.PENDING;
+ private final Semaphore _semaphore = new Semaphore(0);
+ private volatile Runnable _task;
private volatile Thread _thread;
- private boolean isReserved()
- {
- return _state == State.RESERVED;
- }
-
- private Runnable reservedWait()
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{} waiting {}", this, ReservedThreadExecutor.this);
-
- // Keep waiting until stopped, tasked or idle
- while (_count.getLo() >= 0)
- {
- try
- {
- // Always poll at some period as safety to ensure we don't poll forever.
- Runnable task = _queue.poll(_idleTimeNanos, NANOSECONDS);
- if (LOG.isDebugEnabled())
- LOG.debug("{} task={} {}", this, task, ReservedThreadExecutor.this);
- if (task != null)
- return task;
-
- // we have idled out
- int size = _count.getLo();
- // decrement size if we have not also been stopped.
- while (size > 0)
- {
- if (_count.compareAndSetLo(size, --size))
- break;
- size = _count.getLo();
- }
- _state = size >= 0 ? State.IDLE : State.STOPPED;
- return STOP;
- }
- catch (InterruptedException e)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("ignored", e);
- }
- }
- _state = State.STOPPED;
- return STOP;
- }
-
@Override
public void run()
{
_thread = Thread.currentThread();
try
{
+ _pending.decrementAndGet();
+
while (true)
{
- long count = _count.get();
+ int slot = _threads.offer(this);
+ if (LOG.isDebugEnabled())
+ LOG.debug("offered to slot " + slot);
- // reduce pending if this thread was pending
- int pending = getHi(count) - (_state == State.PENDING ? 1 : 0);
- int size = getLo(count);
+ if (slot < 0)
+ // no slot available
+ return;
- State next;
- if (size < 0 || size >= _capacity)
- {
- // The executor has stopped or this thread is excess to capacity
- next = State.STOPPED;
- }
- else
+ if (!isRunning() && _threads.remove(this, slot))
+ return;
+
+ Runnable task = waitForTask();
+ while (task == null)
{
- long now = NanoTime.now();
- long lastEmpty = _lastEmptyNanoTime.get();
- if (size > 0 && _idleTimeNanos < NanoTime.elapsed(lastEmpty, now) && _lastEmptyNanoTime.compareAndSet(lastEmpty, now))
- {
- // it has been too long since we hit zero reserved threads, so are "busy" idle
- next = State.IDLE;
- }
- else
+ if (!isRunning())
+ return;
+
+ // Shrink if we are already removed or there are other reserved threads;
+ // there is a small chance multiple threads will shrink below minSize.
+ if (getAvailable() > _minSize && _threads.remove(this, slot))
{
- // We will become a reserved thread if we can update the count below.
- next = State.RESERVED;
- size++;
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} reservedThread shrank {}", ReservedThreadExecutor.this, this);
+ return;
}
+ task = waitForTask();
}
- // Update count for pending and size
- if (!_count.compareAndSet(count, pending, size))
- continue;
-
- if (LOG.isDebugEnabled())
- LOG.debug("{} was={} next={} size={}+{} capacity={}", this, _state, next, pending, size, _capacity);
- _state = next;
- if (next != State.RESERVED)
- break;
-
- // We are reserved whilst we are waiting for an offered _task.
- Runnable task = reservedWait();
-
- // Is the task the STOP poison pill?
- if (task == STOP)
- break;
-
- // Run the task
try
{
- _state = State.RUNNING;
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} reservedThread run {} on {}", ReservedThreadExecutor.this, task, this);
task.run();
}
- catch (Throwable e)
+ catch (Throwable t)
{
- LOG.warn("Unable to run task", e);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} task {} failure", ReservedThreadExecutor.this, task, t);
}
finally
{
- // Clear any interrupted status.
- Thread.interrupted();
+ // clear interrupted status between reserved thread iterations
+ if (Thread.interrupted() && LOG.isDebugEnabled())
+ LOG.debug("{} was interrupted", _thread);
}
}
}
- finally
+ catch (Throwable t)
{
if (LOG.isDebugEnabled())
- LOG.debug("{} exited {}", this, ReservedThreadExecutor.this);
- _threads.remove(this);
+ LOG.debug("{} reservedThread {} failure", ReservedThreadExecutor.this, this, t);
+ }
+ finally
+ {
_thread = null;
}
}
+ private void wakeup(Runnable task)
+ {
+ _task = task;
+ _semaphore.release();
+ }
+
+ private Runnable waitForTask()
+ {
+ try
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("waiting for task");
+ if (_idleTimeoutMs <= 0)
+ _semaphore.acquire();
+ else if (!_semaphore.tryAcquire(_idleTimeoutMs, TimeUnit.MILLISECONDS))
+ return null;
+ Runnable task = _task;
+ _task = null;
+ return task;
+ }
+ catch (Throwable e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("wait failed ", e);
+ }
+ return null;
+ }
+
+ private void stop()
+ {
+ // If we are stopping, the reserved thread may already have stopped. So just interrupt rather than
+ // expect an exchange rendezvous.
+ _semaphore.release();
+ Thread thread = _thread;
+ if (thread != null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("interrupting thread {} for stop", thread);
+ thread.interrupt();
+ }
+ }
+
@Override
public String toString()
{
- return String.format("%s@%x{%s,thread=%s}",
+ return String.format("%s@%x{thread=%s}",
getClass().getSimpleName(),
hashCode(),
- _state,
_thread);
}
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadIdPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadIdPool.java
new file mode 100644
index 000000000000..84f092567bfc
--- /dev/null
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadIdPool.java
@@ -0,0 +1,251 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.thread;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.eclipse.jetty.util.MemoryUtils;
+import org.eclipse.jetty.util.ProcessorUtils;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.component.DumpableCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A fixed sized pool of items that uses ThreadId to avoid contention.
+ * This class can be used, instead of a {@link ThreadLocal}, when pooling items
+ * that are expensive to create, but only used briefly in the scope of a single thread.
+ * It is safe to use with {@link org.eclipse.jetty.util.VirtualThreads}, as unlike a {@link ThreadLocal} pool,
+ * the number of items is limited.
+ *
This is a light-weight version of {@link org.eclipse.jetty.util.ConcurrentPool} that is best used
+ * when items do not reserve an index in the pool even when acquired.
+ * @see org.eclipse.jetty.util.ConcurrentPool
+ */
+public class ThreadIdPool implements Dumpable
+{
+ private static final Logger LOG = LoggerFactory.getLogger(ThreadIdPool.class);
+
+ // How far the entries in the AtomicReferenceArray are spread apart to avoid false sharing.
+ private static final int SPREAD_FACTOR = MemoryUtils.getReferencesPerCacheLine();
+
+ private final int _capacity;
+ private final AtomicReferenceArray _items;
+
+ public ThreadIdPool()
+ {
+ this(-1);
+ }
+
+ public ThreadIdPool(int capacity)
+ {
+ _capacity = calcCapacity(capacity);
+ _items = new AtomicReferenceArray<>((_capacity + 1) * SPREAD_FACTOR);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{}", this);
+ }
+
+ private static int calcCapacity(int capacity)
+ {
+ if (capacity >= 0)
+ return capacity;
+ return 2 * TypeUtil.ceilToNextPowerOfTwo(ProcessorUtils.availableProcessors());
+ }
+
+ private static int toSlot(int index)
+ {
+ return (index + 1) * SPREAD_FACTOR;
+ }
+
+ /**
+ * @return the maximum number of items
+ */
+ public int capacity()
+ {
+ return _capacity;
+ }
+
+ /**
+ * @return the number of items available
+ */
+ public int size()
+ {
+ int available = 0;
+ for (int i = 0; i < capacity(); i++)
+ {
+ if (_items.getPlain(toSlot(i)) != null)
+ available++;
+ }
+ return available;
+ }
+
+ /**
+ * Offer an item to the pool.
+ * @param e The item to offer
+ * @return The index the item was added at or -1, if it was not added
+ * @see #remove(Object, int)
+ */
+ public int offer(E e)
+ {
+ int capacity = capacity();
+ if (capacity > 0)
+ {
+ int index = (int)(Thread.currentThread().getId() % capacity);
+ for (int i = 0; i < capacity; i++)
+ {
+ if (_items.compareAndSet(toSlot(index), null, e))
+ return index;
+ if (++index == capacity)
+ index = 0;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Take an item from the pool.
+ * @return The taken item or null if none available.
+ */
+ public E take()
+ {
+ int capacity = capacity();
+ if (capacity == 0)
+ return null;
+ int index = (int)(Thread.currentThread().getId() % capacity);
+ for (int i = 0; i < capacity; i++)
+ {
+ E e = _items.getAndSet(toSlot(index), null);
+ if (e != null)
+ return e;
+ if (++index == capacity)
+ index = 0;
+ }
+ return null;
+ }
+
+ /**
+ * Remove a specific item from the pool from a specific index
+ * @param e The item to remove
+ * @param index The index the item was given to, as returned by {@link #offer(Object)}
+ * @return {@code True} if the item was in the pool and was able to be removed.
+ */
+ public boolean remove(E e, int index)
+ {
+ if (index < 0)
+ throw new IndexOutOfBoundsException();
+ return _items.compareAndSet(toSlot(index), e, null);
+ }
+
+ /**
+ * Removes all items from the pool.
+ * @return A list of all removed items
+ */
+ public List removeAll()
+ {
+ int capacity = capacity();
+ List all = new ArrayList<>(capacity);
+ for (int i = 0; i < capacity; i++)
+ {
+ E e = _items.getAndSet(toSlot(i), null);
+ if (e != null)
+ all.add(e);
+ }
+ return all;
+ }
+
+ /**
+ * Take an item with a {@link #take()} operation, else if that returns null then use the {@code supplier} (which may
+ * construct a new instance).
+ * @param supplier The supplier for an item to be used if an item cannot be taken from the pool.
+ * @return An item, never null.
+ */
+ public E takeOrElse(Supplier supplier)
+ {
+ E e = take();
+ return e == null ? supplier.get() : e;
+ }
+
+ /**
+ * Apply an item, either from the pool or supplier, to a function, then give it back to the pool.
+ * This is equivalent of {@link #takeOrElse(Supplier)}; then {@link Function#apply(Object)};
+ * followed by {@link #offer(Object)}.
+ * @param supplier The supplier for an item to be used if an item cannot be taken from the pool.
+ * @param function A function producing a result from an item. This may be
+ * a method reference to a method on the item taking no arguments and producing a result.
+ * @param The type of the function return
+ * @return Te result of the function applied to the item and the argument
+ */
+ public R apply(Supplier supplier, Function function)
+ {
+ E e = takeOrElse(supplier);
+ try
+ {
+ return function.apply(e);
+ }
+ finally
+ {
+ offer(e);
+ }
+ }
+
+ /**
+ * Apply an item, either from the pool or supplier, to a function, then give it back to the pool.
+ * This is equivalent of {@link #takeOrElse(Supplier)}; then {@link BiFunction#apply(Object, Object)};
+ * followed by {@link #offer(Object)}.
+ * @param supplier The supplier for an item to be used if an item cannot be taken from the pool.
+ * @param function A function producing a result from an item and an argument. This may be
+ * a method reference to a method on the item taking an argument and producing a result.
+ * @param argument The argument to pass to the function.
+ * @param The type of the function argument
+ * @param The type of the function return
+ * @return Te result of the function applied to the item and the argument
+ */
+ public R apply(Supplier supplier, BiFunction function, A argument)
+ {
+ E e = takeOrElse(supplier);
+ try
+ {
+ return function.apply(e, argument);
+ }
+ finally
+ {
+ offer(e);
+ }
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ int capacity = capacity();
+ List
+
+ commons-codec
+ commons-codec
+ test
+ org.eclipse.jettyjetty-client
diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/ClassMatcher.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/ClassMatcher.java
index f7334e8c337f..a79a8783fb9a 100644
--- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/ClassMatcher.java
+++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/ClassMatcher.java
@@ -751,6 +751,9 @@ public boolean match(Class> clazz)
public boolean match(String name, URL url)
{
+ if (url == null)
+ return false;
+
// Strip class suffix for name matching
if (name.endsWith(".class"))
name = name.substring(0, name.length() - 6);
diff --git a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
index 7e89fec12c38..0da93aa322fb 100644
--- a/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
+++ b/jetty-ee10/jetty-ee10-webapp/src/main/java/org/eclipse/jetty/ee10/webapp/WebAppContext.java
@@ -1220,14 +1220,14 @@ protected void startContext()
{
//resolve the metadata
_metadata.resolve(this);
- super.startContext();
+ startWebapp();
}
}
@Override
protected void stopContext() throws Exception
{
- super.stopContext();
+ stopWebapp();
try
{
for (int i = _configurations.size(); i-- > 0; )
@@ -1253,6 +1253,24 @@ protected void stopContext() throws Exception
}
}
+ /**
+ * Continue the {@link #startContext()} before calling {@code super.startContext()}.
+ * @throws Exception If there was a problem starting
+ */
+ protected void startWebapp() throws Exception
+ {
+ super.startContext();
+ }
+
+ /**
+ * Continue the {@link #stopContext()} before calling {@code super.stopContext()}.
+ * @throws Exception If there was a problem stopping
+ */
+ protected void stopWebapp() throws Exception
+ {
+ super.stopContext();
+ }
+
@Override
public Set setServletSecurity(Dynamic registration, ServletSecurityElement servletSecurityElement)
{
@@ -1364,7 +1382,7 @@ public URL getResource(String path) throws MalformedURLException
// If a WAR file is mounted, or is extracted to a temp directory,
// then the first entry of the resource base must be the WAR file.
Resource resource = WebAppContext.this.getResource(path);
- if (resource == null)
+ if (Resources.missing(resource))
return null;
for (Resource r: resource)
diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketContainer.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketContainer.java
index ce2d71a1bd8d..c915d64cf816 100644
--- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketContainer.java
+++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee10/websocket/jakarta/common/JakartaWebSocketContainer.java
@@ -46,7 +46,7 @@ public JakartaWebSocketContainer(WebSocketComponents components)
{
this.components = components;
addSessionListener(sessionTracker);
- addBean(sessionTracker);
+ installBean(sessionTracker);
}
public abstract Executor getExecutor();
diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java
index 601ee44a1014..d9e329f0cdbb 100644
--- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java
+++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee10/websocket/server/JettyWebSocketServerContainer.java
@@ -134,10 +134,10 @@ public String toString()
this.components = components;
this.executor = executor;
this.frameHandlerFactory = new JettyServerFrameHandlerFactory(this, components);
- addBean(frameHandlerFactory);
+ installBean(frameHandlerFactory);
addSessionListener(sessionTracker);
- addBean(sessionTracker);
+ installBean(sessionTracker);
}
public void addMapping(String pathSpec, JettyWebSocketCreator creator)
diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketOverHTTP2Test.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketOverHTTP2Test.java
index 46e93aa6deaa..755b4a1889cb 100644
--- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketOverHTTP2Test.java
+++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-tests/src/test/java/org/eclipse/jetty/ee10/websocket/tests/WebSocketOverHTTP2Test.java
@@ -38,6 +38,7 @@
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServletFactory;
import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.ee10.websocket.server.internal.DelegatedServerUpgradeRequest;
+import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http2.ErrorCode;
import org.eclipse.jetty.http2.HTTP2Cipher;
@@ -129,7 +130,7 @@ private void startServer(TestJettyWebSocketServlet servlet) throws Exception
server.setHandler(new EventsHandler(server.getHandler())
{
@Override
- protected void onComplete(Request request, Throwable failure)
+ protected void onComplete(Request request, int status, HttpFields headers, Throwable failure)
{
if (onComplete != null)
onComplete.run();
diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
index c6eee3a973c5..b6627296f501 100644
--- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
+++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
@@ -267,7 +267,7 @@ protected ContextHandler(APIContext context,
String contextPath)
{
_coreContextHandler = new CoreContextHandler();
- addBean(_coreContextHandler, false);
+ installBean(_coreContextHandler, false);
_apiContext = context == null ? new APIContext() : context;
_initParams = new HashMap<>();
if (contextPath != null)
@@ -2539,7 +2539,7 @@ public class CoreContextHandler extends org.eclipse.jetty.server.handler.Context
CoreContextHandler()
{
super.setHandler(new CoreToNestedHandler());
- addBean(ContextHandler.this, true);
+ installBean(ContextHandler.this, true);
}
@Override
diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java
index 62744880a717..602b417e88f7 100644
--- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java
+++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/HttpOutput.java
@@ -44,6 +44,7 @@
import org.eclipse.jetty.util.SharedBlockingCallback;
import org.eclipse.jetty.util.SharedBlockingCallback.Blocker;
import org.eclipse.jetty.util.thread.AutoLock;
+import org.eclipse.jetty.util.thread.ThreadIdPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -176,7 +177,7 @@ default void resetBuffer() throws IllegalStateException
}
private static final Logger LOG = LoggerFactory.getLogger(HttpOutput.class);
- private static final ThreadLocal _encoder = new ThreadLocal<>();
+ private static final ThreadIdPool _encoder = new ThreadIdPool<>();
private final HttpChannel _channel;
private final HttpChannelState _channelState;
@@ -1069,13 +1070,12 @@ private void print(String s, boolean eoln) throws IOException
s = String.valueOf(s);
String charset = _channel.getResponse().getCharacterEncoding();
- CharsetEncoder encoder = _encoder.get();
+ CharsetEncoder encoder = _encoder.take();
if (encoder == null || !encoder.charset().name().equalsIgnoreCase(charset))
{
encoder = Charset.forName(charset).newEncoder();
encoder.onMalformedInput(CodingErrorAction.REPLACE);
encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
- _encoder.set(encoder);
}
else
{
@@ -1136,6 +1136,8 @@ else if (crlf != null && crlf.hasRemaining())
finally
{
out.release();
+ encoder.reset();
+ _encoder.offer(encoder);
}
}
diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java
index 1edaa2b7fe1f..2bfb27344213 100644
--- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java
+++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java
@@ -69,11 +69,11 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab
public SessionHandler()
{
setSessionTrackingModes(DEFAULT_SESSION_TRACKING_MODES);
- addBean(_sessionManager);
- addBean(_cookieConfig);
- addBean(_sessionListeners);
- addBean(_sessionIdListeners);
- addBean(_sessionAttributeListeners);
+ installBean(_sessionManager);
+ installBean(_cookieConfig);
+ installBean(_sessionListeners);
+ installBean(_sessionIdListeners);
+ installBean(_sessionAttributeListeners);
}
public SessionManager getSessionManager()
diff --git a/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/EE9Activator.java b/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/EE9Activator.java
index 9f4eb0705f28..9ca5afcf7986 100644
--- a/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/EE9Activator.java
+++ b/jetty-ee9/jetty-ee9-osgi/jetty-ee9-osgi-boot/src/main/java/org/eclipse/jetty/ee9/osgi/boot/EE9Activator.java
@@ -16,7 +16,6 @@
import java.io.File;
import java.net.URI;
import java.net.URL;
-import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
diff --git a/jetty-ee9/jetty-ee9-security/src/main/java/org/eclipse/jetty/ee9/security/SecurityHandler.java b/jetty-ee9/jetty-ee9-security/src/main/java/org/eclipse/jetty/ee9/security/SecurityHandler.java
index ff92704b8345..a3092792b3fd 100644
--- a/jetty-ee9/jetty-ee9-security/src/main/java/org/eclipse/jetty/ee9/security/SecurityHandler.java
+++ b/jetty-ee9/jetty-ee9-security/src/main/java/org/eclipse/jetty/ee9/security/SecurityHandler.java
@@ -84,7 +84,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti
protected SecurityHandler()
{
- addBean(new DumpableCollection("knownAuthenticatorFactories", __knownAuthenticatorFactories));
+ installBean(new DumpableCollection("knownAuthenticatorFactories", __knownAuthenticatorFactories));
}
/**
diff --git a/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/ServletContextHandler.java b/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/ServletContextHandler.java
index d819163e7c27..a411a03cb313 100644
--- a/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/ServletContextHandler.java
+++ b/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/ServletContextHandler.java
@@ -173,7 +173,7 @@ public ServletContextHandler(Container parent, String contextPath, SessionHandle
_servletHandler = servletHandler;
_objFactory = new DecoratedObjectFactory();
- addBean(_objFactory, true);
+ installBean(_objFactory, true);
// Link the handlers
relinkHandlers();
diff --git a/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/ServletTester.java b/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/ServletTester.java
index 90662c0e17c4..7541d0f0f11d 100644
--- a/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/ServletTester.java
+++ b/jetty-ee9/jetty-ee9-servlet/src/main/java/org/eclipse/jetty/ee9/servlet/ServletTester.java
@@ -229,7 +229,7 @@ public ServletTester(String contextPath, int options)
{
_context = new ServletContextHandler(_server, contextPath, options);
_server.setConnectors(new Connector[]{_connector});
- addBean(_server);
+ installBean(_server);
}
public ServletContextHandler getContext()
diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-loginservice/pom.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-loginservice/pom.xml
index fc720cb5947e..fe667eb00d13 100644
--- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-loginservice/pom.xml
+++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-loginservice/pom.xml
@@ -33,6 +33,11 @@
org.eclipse.jetty.ee9jetty-ee9-webapp
+
+ commons-codec
+ commons-codec
+ test
+ net.java.dev.jnajna
diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-gcloud/pom.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-gcloud/pom.xml
index c24b6f30b3ec..e5b0934f3d55 100644
--- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-gcloud/pom.xml
+++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-gcloud/pom.xml
@@ -12,6 +12,11 @@
${project.groupId}.sessions.gcloud
+
+ commons-codec
+ commons-codec
+ test
+ org.eclipse.jettyjetty-client
diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-infinispan/pom.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-infinispan/pom.xml
index 8cc7560e0054..8b5d97b6d703 100644
--- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-infinispan/pom.xml
+++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-infinispan/pom.xml
@@ -21,6 +21,11 @@
${gson.version}test
+
+ commons-codec
+ commons-codec
+ test
+ org.eclipse.jettyjetty-client
diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-jdbc/pom.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-jdbc/pom.xml
index a802fe564359..692b9ac53302 100644
--- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-jdbc/pom.xml
+++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-jdbc/pom.xml
@@ -12,6 +12,11 @@
${project.groupId}.sessions.jdbc
+
+ commons-codec
+ commons-codec
+ test
+ org.eclipse.jettyjetty-client
diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-memcached/pom.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-memcached/pom.xml
index ef847181d8c2..d53b2facac1f 100644
--- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-memcached/pom.xml
+++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-memcached/pom.xml
@@ -12,6 +12,11 @@
${project.groupId}.sessions.memcached
+
+ commons-codec
+ commons-codec
+ test
+ org.eclipse.jettyjetty-client
diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-mongodb/pom.xml b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-mongodb/pom.xml
index 6ad26cbe2741..05f41e8a461b 100644
--- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-mongodb/pom.xml
+++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-mongodb/pom.xml
@@ -12,6 +12,11 @@
${project.groupId}.sessions.mongo
+
+ commons-codec
+ commons-codec
+ test
+ org.eclipse.jettyjetty-client
diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/ClassMatcher.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/ClassMatcher.java
index 148486e5ba42..84d05c2a9cf9 100644
--- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/ClassMatcher.java
+++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/ClassMatcher.java
@@ -733,6 +733,9 @@ public boolean match(Class> clazz)
public boolean match(String name, URL url)
{
+ if (url == null)
+ return false;
+
// Strip class suffix for name matching
if (name.endsWith(".class"))
name = name.substring(0, name.length() - 6);
diff --git a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java
index c76a53dfe2f7..2873a06f9d95 100644
--- a/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java
+++ b/jetty-ee9/jetty-ee9-webapp/src/main/java/org/eclipse/jetty/ee9/webapp/WebAppContext.java
@@ -1294,14 +1294,14 @@ protected void startContext()
{
//resolve the metadata
_metadata.resolve(this);
- super.startContext();
+ startWebapp();
}
}
@Override
protected void stopContext() throws Exception
{
- super.stopContext();
+ stopWebapp();
try
{
for (int i = _configurations.size(); i-- > 0; )
@@ -1327,6 +1327,24 @@ protected void stopContext() throws Exception
}
}
+ /**
+ * Continue the {@link #startContext()} before calling {@code super.startContext()}.
+ * @throws Exception If there was a problem starting
+ */
+ protected void startWebapp() throws Exception
+ {
+ super.startContext();
+ }
+
+ /**
+ * Continue the {@link #stopContext()} before calling {@code super.stopContext()}.
+ * @throws Exception If there was a problem stopping
+ */
+ protected void stopWebapp() throws Exception
+ {
+ super.stopContext();
+ }
+
@Override
public Set setServletSecurity(Dynamic registration, ServletSecurityElement servletSecurityElement)
{
@@ -1429,7 +1447,7 @@ public URL getResource(String path) throws MalformedURLException
// If a WAR file is mounted, or is extracted to a temp directory,
// then the first entry of the resource base must be the WAR file.
Resource resource = WebAppContext.this.getResource(path);
- if (resource == null)
+ if (Resources.missing(resource))
return null;
for (Resource r: resource)
diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketContainer.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketContainer.java
index 10cb7e004d40..0f94e7ee736a 100644
--- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketContainer.java
+++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common/src/main/java/org/eclipse/jetty/ee9/websocket/jakarta/common/JakartaWebSocketContainer.java
@@ -46,7 +46,7 @@ public JakartaWebSocketContainer(WebSocketComponents components)
{
this.components = components;
addSessionListener(sessionTracker);
- addBean(sessionTracker);
+ installBean(sessionTracker);
}
public abstract Executor getExecutor();
diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/WebSocketClient.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/WebSocketClient.java
index fcd6b49ad7fd..c5fc52e9797e 100644
--- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/WebSocketClient.java
+++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/WebSocketClient.java
@@ -85,7 +85,7 @@ public WebSocketClient(HttpClient httpClient)
addManaged(coreClient);
frameHandlerFactory = new JettyWebSocketFrameHandlerFactory(this, components);
sessionListeners.add(sessionTracker);
- addBean(sessionTracker);
+ installBean(sessionTracker);
}
public CompletableFuture connect(Object websocket, URI toUri) throws IOException
diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServerContainer.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServerContainer.java
index baae7a52143e..f57c41314736 100644
--- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServerContainer.java
+++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server/src/main/java/org/eclipse/jetty/ee9/websocket/server/JettyWebSocketServerContainer.java
@@ -137,10 +137,10 @@ public String toString()
this.components = components;
this.executor = executor;
this.frameHandlerFactory = new JettyServerFrameHandlerFactory(this, components);
- addBean(frameHandlerFactory);
+ installBean(frameHandlerFactory);
addSessionListener(sessionTracker);
- addBean(sessionTracker);
+ installBean(sessionTracker);
}
public void addMapping(String pathSpec, JettyWebSocketCreator creator)
diff --git a/jetty-integrations/jetty-gcloud/pom.xml b/jetty-integrations/jetty-gcloud/pom.xml
index 0341d624af8a..98a929c4feb6 100644
--- a/jetty-integrations/jetty-gcloud/pom.xml
+++ b/jetty-integrations/jetty-gcloud/pom.xml
@@ -17,8 +17,8 @@
- 2.17.6
- 3.25.1
+ 2.18.4
+ 3.25.2
diff --git a/jetty-integrations/jetty-infinispan/jetty-infinispan-remote-query/pom.xml b/jetty-integrations/jetty-infinispan/jetty-infinispan-remote-query/pom.xml
index 33a8a36b4505..29ae3115fe88 100644
--- a/jetty-integrations/jetty-infinispan/jetty-infinispan-remote-query/pom.xml
+++ b/jetty-integrations/jetty-infinispan/jetty-infinispan-remote-query/pom.xml
@@ -48,6 +48,11 @@
org.infinispaninfinispan-remote-query-client
+
+ commons-codec
+ commons-codec
+ test
+ org.slf4jslf4j-simple
diff --git a/pom.xml b/pom.xml
index 60000d702855..7559b5cb6050 100644
--- a/pom.xml
+++ b/pom.xml
@@ -155,9 +155,9 @@
1.11.34.5.144.4.16
- 2.2.4
+ 2.2.6false
- 2.2.14
+ 2.3.02.5.119.64.2.0
@@ -165,10 +165,10 @@
3.5.01.53.2.0
- 10.13.0
- 1.16.0
+ 10.14.0
+ 1.16.13.14.0
- 1.25.0
+ 1.26.02.15.11717
@@ -179,10 +179,10 @@
3.33.07.0.53.0.2
- 1.5.0
- 2.24.1
+ 1.6.0
+ 2.25.04.0.6
- 1.60.1
+ 1.62.22.10.133.0.0-jre7.0.0
@@ -204,7 +204,7 @@
2.2.1.Final3.5.3.Final1.1
- 0.20.0
+ 0.20.11.22.71.0.7
@@ -216,6 +216,7 @@
1.4${project.build.directory}/${jettyHomeZipFileName}jetty-home.zip
+ 1.3.131.37benchmarks5.14.0
@@ -227,17 +228,17 @@
concurrentsame_threadtrue
- 5.10.0
+ 5.10.22.0.34.3${project.build.directory}/local-repo
- 2.22.1
- 1.4.14
+ 2.23.0
+ 1.5.110.3.6
- 3.3.2
+ 3.3.30.13.11.1.0
- 3.10.2
+ 3.11.03.1.03.6.05.1.9
@@ -249,7 +250,7 @@
3.1.13.9.43.4.1
- 3.1.1
+ 3.2.03.1.03.1.13.6.0
@@ -260,7 +261,7 @@
3.1.01.9.183.3.1
- 3.5.1
+ 3.5.23.3.03.1.2
@@ -297,15 +298,15 @@
src/it/settings.xml2.0.91.3.6
- 4.8.2.0
+ 4.8.3.1false01.8.3
- 1.19.3
+ 1.19.63.0.02.16.21.7.0.Final
- 2.2.2.Final
+ 2.3.1.Final2.4.8
@@ -356,6 +357,11 @@
logback-core${logback.version}
+
+ com.github.jnr
+ jffi
+ ${jffi.version}
+ com.google.code.findbugsjsr305
diff --git a/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadExecutorExchanger.java b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadExecutorExchanger.java
new file mode 100644
index 000000000000..c990b5843d12
--- /dev/null
+++ b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadExecutorExchanger.java
@@ -0,0 +1,388 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.thread.jmh;
+
+import java.io.IOException;
+import java.util.concurrent.Exchanger;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.jetty.util.ProcessorUtils;
+import org.eclipse.jetty.util.VirtualThreads;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.thread.ThreadIdPool;
+import org.eclipse.jetty.util.thread.ThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPoolBudget;
+import org.eclipse.jetty.util.thread.TryExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
A TryExecutor using pre-allocated/reserved threads from an external Executor.
+ *
Calls to {@link #tryExecute(Runnable)} on ReservedThreadExecutor will either
+ * succeed with a reserved thread immediately being assigned the task, or fail if
+ * no reserved thread is available.
+ *
Threads are reserved lazily, with new reserved threads being allocated from the external
+ * {@link Executor} passed to the constructor. Whenever 1 or more reserved threads have been
+ * idle for more than {@link #getIdleTimeoutMs()} then one reserved thread will return to
+ * the external Executor.
+ */
+@ManagedObject("A pool for reserved threads")
+public class ReservedThreadExecutorExchanger extends ContainerLifeCycle implements TryExecutor, Dumpable
+{
+ private static final Logger LOG = LoggerFactory.getLogger(ReservedThreadExecutorExchanger.class);
+
+ private final Executor _executor;
+ private final ThreadIdPool _threads;
+ private final AtomicInteger _pending = new AtomicInteger();
+ private final int _minSize;
+ private final int _maxPending;
+ private ThreadPoolBudget.Lease _lease;
+ private long _idleTimeoutMs;
+
+ /**
+ * @param executor The executor to use to obtain threads
+ * @param capacity The number of threads that can be reserved. If less than 0 then capacity
+ * is calculated based on a heuristic from the number of available processors and
+ * thread pool type.
+ */
+ public ReservedThreadExecutorExchanger(Executor executor, int capacity)
+ {
+ this(executor, capacity, -1);
+ }
+
+ /**
+ * @param executor The executor to use to obtain threads
+ * @param capacity The number of threads that can be reserved. If less than 0 then capacity
+ * is calculated based on a heuristic from the number of available processors and
+ * thread pool type.
+ * @param minSize The minimum number of reserve Threads that the algorithm tries to maintain, or -1 for a heuristic value.
+ */
+ public ReservedThreadExecutorExchanger(Executor executor, int capacity, int minSize)
+ {
+ this(executor, capacity, minSize, -1);
+ }
+
+ /**
+ * @param executor The executor to use to obtain threads
+ * @param capacity The number of threads that can be reserved. If less than 0 then capacity
+ * is calculated based on a heuristic from the number of available processors and
+ * thread pool type.
+ * @param minSize The minimum number of reserve Threads that the algorithm tries to maintain, or -1 for a heuristic value.
+ * @param maxPending The maximum number of reserved Threads to start, or -1 for no limit.
+ */
+ public ReservedThreadExecutorExchanger(Executor executor, int capacity, int minSize, int maxPending)
+ {
+ _executor = executor;
+ _threads = new ThreadIdPool<>(reservedThreads(executor, capacity));
+ _minSize = minSize < 0 ? Math.min(1, _threads.capacity()) : minSize;
+ if (_minSize > _threads.capacity())
+ throw new IllegalArgumentException("minSize larger than capacity");
+ _maxPending = maxPending;
+ if (_maxPending == 0)
+ throw new IllegalArgumentException("maxPending cannot be 0");
+ if (LOG.isDebugEnabled())
+ LOG.debug("{}", this);
+ installBean(_executor);
+ installBean(_threads);
+ }
+
+ /**
+ * Get the heuristic number of reserved threads.
+ *
+ * @param executor The executor to use to obtain threads
+ * @param capacity The number of threads to preallocate, If less than 0 then capacity
+ * is calculated based on a heuristic from the number of available processors and
+ * thread pool size.
+ * @return the number of reserved threads that would be used by a ReservedThreadExecutor
+ * constructed with these arguments.
+ */
+ public static int reservedThreads(Executor executor, int capacity)
+ {
+ if (capacity >= 0)
+ return capacity;
+ if (VirtualThreads.isUseVirtualThreads(executor))
+ return 0;
+ int cpus = ProcessorUtils.availableProcessors();
+ if (executor instanceof ThreadPool.SizedThreadPool)
+ {
+ int threads = ((ThreadPool.SizedThreadPool)executor).getMaxThreads();
+ return Math.max(1, Math.min(cpus, threads / 8));
+ }
+ return cpus;
+ }
+
+ public Executor getExecutor()
+ {
+ return _executor;
+ }
+
+ /**
+ * @return the maximum number of reserved threads
+ */
+ @ManagedAttribute(value = "max number of reserved threads", readonly = true)
+ public int getCapacity()
+ {
+ return _threads.capacity();
+ }
+
+ /**
+ * @return the number of threads available to {@link #tryExecute(Runnable)}
+ */
+ @ManagedAttribute(value = "available reserved threads", readonly = true)
+ public int getAvailable()
+ {
+ return _threads.size();
+ }
+
+ @ManagedAttribute(value = "pending reserved threads (deprecated)", readonly = true)
+ @Deprecated
+ public int getPending()
+ {
+ return 0;
+ }
+
+ @ManagedAttribute(value = "idle timeout in ms", readonly = true)
+ public long getIdleTimeoutMs()
+ {
+ return _idleTimeoutMs;
+ }
+
+ /**
+ * Set the idle timeout for shrinking the reserved thread pool
+ *
+ * @param idleTime Time to wait before shrinking, or 0 for default timeout.
+ * @param idleTimeUnit Time units for idle timeout
+ */
+ public void setIdleTimeout(long idleTime, TimeUnit idleTimeUnit)
+ {
+ if (isRunning())
+ throw new IllegalStateException();
+ _idleTimeoutMs = idleTime <= 0 ? 0 : idleTimeUnit.toMillis(idleTime);
+ }
+
+ @Override
+ public void doStart() throws Exception
+ {
+ _lease = ThreadPoolBudget.leaseFrom(getExecutor(), this, getCapacity());
+ super.doStart();
+ }
+
+ @Override
+ public void doStop() throws Exception
+ {
+ if (_lease != null)
+ _lease.close();
+
+ super.doStop();
+
+ _threads.removeAll().forEach(ReservedThread::stop);
+ }
+
+ @Override
+ public void execute(Runnable task) throws RejectedExecutionException
+ {
+ _executor.execute(task);
+ }
+
+ /**
+ *
Executes the given task if and only if a reserved thread is available.
+ *
+ * @param task the task to run
+ * @return true if and only if a reserved thread was available and has been assigned the task to run.
+ */
+ @Override
+ public boolean tryExecute(Runnable task)
+ {
+ if (task == null)
+ return false;
+
+ ReservedThread reserved = _threads.take();
+ if (reserved != null)
+ return reserved.wakeup(task);
+
+ startReservedThread();
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} tryExecute failed for {}", this, task);
+ return false;
+ }
+
+ private void startReservedThread()
+ {
+ if (_maxPending > 0 && _pending.incrementAndGet() >= _maxPending)
+ {
+ _pending.decrementAndGet();
+ return;
+ }
+
+ try
+ {
+ ReservedThread thread = new ReservedThread();
+ _executor.execute(thread);
+ }
+ catch (Throwable e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("ignored", e);
+ }
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ Dumpable.dumpObjects(out, indent, this);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{capacity=%d,threads=%s}",
+ getClass().getSimpleName(),
+ hashCode(),
+ getCapacity(),
+ _threads);
+ }
+
+ private class ReservedThread implements Runnable
+ {
+ private final Exchanger _exchanger = new Exchanger<>();
+ private volatile Thread _thread;
+
+ @Override
+ public void run()
+ {
+ _thread = Thread.currentThread();
+ boolean pending = true;
+ try
+ {
+ while (true)
+ {
+ int slot = _threads.offer(this);
+
+ if (!isRunning() && _threads.remove(this, slot))
+ break;
+
+ if (pending)
+ {
+ pending = false;
+ _pending.decrementAndGet();
+ }
+
+ if (slot < 0)
+ // no slot available
+ return;
+
+ Runnable task = waitForTask();
+ while (task == null)
+ {
+ if (!isRunning())
+ return;
+
+ // shrink if we are already removed or there are other reserved threads.
+ // There is a small chance multiple threads will shrink below minSize
+ if (getAvailable() > _minSize && _threads.remove(this, slot))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} reservedThread shrank {}", ReservedThreadExecutorExchanger.this, this);
+ return;
+ }
+ task = waitForTask();
+ }
+
+ try
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} reservedThread run {} on {}", ReservedThreadExecutorExchanger.this, task, this);
+ task.run();
+ }
+ catch (Throwable t)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} task {} failure", ReservedThreadExecutorExchanger.this, task, t);
+ }
+ }
+ }
+ catch (Throwable t)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} reservedThread {} failure", ReservedThreadExecutorExchanger.this, this, t);
+ }
+ finally
+ {
+ _thread = null;
+ // Clear any interrupted status.
+ if (Thread.interrupted() && LOG.isDebugEnabled())
+ LOG.debug("interrupted {}", this);
+ }
+ }
+
+ private boolean wakeup(Runnable task)
+ {
+ try
+ {
+ if (_idleTimeoutMs <= 0)
+ _exchanger.exchange(task);
+ else
+ _exchanger.exchange(task, _idleTimeoutMs, TimeUnit.MILLISECONDS);
+ return true;
+ }
+ catch (Throwable e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("exchange failed", e);
+ }
+ return false;
+ }
+
+ private Runnable waitForTask()
+ {
+ try
+ {
+ if (_idleTimeoutMs <= 0)
+ return _exchanger.exchange(null);
+ return _exchanger.exchange(null, _idleTimeoutMs, TimeUnit.MILLISECONDS);
+ }
+ catch (Throwable e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("wait failed ", e);
+ }
+ return null;
+ }
+
+ private void stop()
+ {
+ // If we are stopping, the reserved thread may already have stopped. So just interrupt rather than
+ // expect an exchange rendezvous.
+ Thread thread = _thread;
+ if (thread != null)
+ thread.interrupt();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{thread=%s}",
+ getClass().getSimpleName(),
+ hashCode(),
+ _thread);
+ }
+ }
+}
diff --git a/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadExecutorSemaphore.java b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadExecutorSemaphore.java
new file mode 100644
index 000000000000..e2c6b838caf0
--- /dev/null
+++ b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadExecutorSemaphore.java
@@ -0,0 +1,36 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.thread.jmh;
+
+import java.util.concurrent.Executor;
+
+import org.eclipse.jetty.util.thread.ReservedThreadExecutor;
+
+public class ReservedThreadExecutorSemaphore extends ReservedThreadExecutor
+{
+ public ReservedThreadExecutorSemaphore(Executor executor, int capacity)
+ {
+ super(executor, capacity);
+ }
+
+ public ReservedThreadExecutorSemaphore(Executor executor, int capacity, int minSize)
+ {
+ super(executor, capacity, minSize);
+ }
+
+ public ReservedThreadExecutorSemaphore(Executor executor, int capacity, int minSize, int maxPending)
+ {
+ super(executor, capacity, minSize, maxPending);
+ }
+}
diff --git a/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadExecutorSyncQueue.java b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadExecutorSyncQueue.java
new file mode 100644
index 000000000000..272ae03d9ced
--- /dev/null
+++ b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadExecutorSyncQueue.java
@@ -0,0 +1,435 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.thread.jmh;
+
+import java.io.IOException;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import org.eclipse.jetty.util.AtomicBiInteger;
+import org.eclipse.jetty.util.NanoTime;
+import org.eclipse.jetty.util.ProcessorUtils;
+import org.eclipse.jetty.util.VirtualThreads;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.AbstractLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.component.DumpableCollection;
+import org.eclipse.jetty.util.thread.ThreadPool;
+import org.eclipse.jetty.util.thread.ThreadPoolBudget;
+import org.eclipse.jetty.util.thread.TryExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.eclipse.jetty.util.AtomicBiInteger.getHi;
+import static org.eclipse.jetty.util.AtomicBiInteger.getLo;
+
+@ManagedObject("A pool for reserved threads")
+public class ReservedThreadExecutorSyncQueue extends AbstractLifeCycle implements TryExecutor, Dumpable
+{
+ private static final Logger LOG = LoggerFactory.getLogger(ReservedThreadExecutorSyncQueue.class);
+ private static final long DEFAULT_IDLE_TIMEOUT = TimeUnit.MINUTES.toNanos(1);
+ private static final Runnable STOP = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ }
+
+ @Override
+ public String toString()
+ {
+ return "STOP";
+ }
+ };
+
+ private final Executor _executor;
+ private final int _capacity;
+ private final Set _threads = ConcurrentHashMap.newKeySet();
+ private final SynchronousQueue _queue = new SynchronousQueue<>(false);
+ private final AtomicBiInteger _count = new AtomicBiInteger(); // hi=pending; lo=size;
+ private final AtomicLong _lastEmptyNanoTime = new AtomicLong(NanoTime.now());
+ private ThreadPoolBudget.Lease _lease;
+ private long _idleTimeNanos = DEFAULT_IDLE_TIMEOUT;
+
+ /**
+ * @param executor The executor to use to obtain threads
+ * @param capacity The number of threads to preallocate. If less than 0 then capacity
+ * is calculated based on a heuristic from the number of available processors and
+ * thread pool size.
+ */
+ public ReservedThreadExecutorSyncQueue(Executor executor, int capacity)
+ {
+ _executor = executor;
+ _capacity = reservedThreads(executor, capacity);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{}", this);
+ }
+
+ /**
+ * @param executor The executor to use to obtain threads
+ * @param capacity The number of threads to preallocate, If less than 0 then capacity
+ * is calculated based on a heuristic from the number of available processors and
+ * thread pool size.
+ * @return the number of reserved threads that would be used by a ReservedThreadExecutor
+ * constructed with these arguments.
+ */
+ private static int reservedThreads(Executor executor, int capacity)
+ {
+ if (capacity >= 0)
+ return capacity;
+ if (VirtualThreads.isUseVirtualThreads(executor))
+ return 0;
+ int cpus = ProcessorUtils.availableProcessors();
+ if (executor instanceof ThreadPool.SizedThreadPool)
+ {
+ int threads = ((ThreadPool.SizedThreadPool)executor).getMaxThreads();
+ return Math.max(1, Math.min(cpus, threads / 8));
+ }
+ return cpus;
+ }
+
+ public Executor getExecutor()
+ {
+ return _executor;
+ }
+
+ /**
+ * @return the maximum number of reserved threads
+ */
+ @ManagedAttribute(value = "max number of reserved threads", readonly = true)
+ public int getCapacity()
+ {
+ return _capacity;
+ }
+
+ /**
+ * @return the number of threads available to {@link #tryExecute(Runnable)}
+ */
+ @ManagedAttribute(value = "available reserved threads", readonly = true)
+ public int getAvailable()
+ {
+ return _count.getLo();
+ }
+
+ @ManagedAttribute(value = "pending reserved threads", readonly = true)
+ public int getPending()
+ {
+ return _count.getHi();
+ }
+
+ @ManagedAttribute(value = "idle timeout in ms", readonly = true)
+ public long getIdleTimeoutMs()
+ {
+ return NANOSECONDS.toMillis(_idleTimeNanos);
+ }
+
+ /**
+ * Set the idle timeout for shrinking the reserved thread pool
+ *
+ * @param idleTime Time to wait before shrinking, or 0 for default timeout.
+ * @param idleTimeUnit Time units for idle timeout
+ */
+ public void setIdleTimeout(long idleTime, TimeUnit idleTimeUnit)
+ {
+ if (isRunning())
+ throw new IllegalStateException();
+ _idleTimeNanos = (idleTime <= 0 || idleTimeUnit == null) ? DEFAULT_IDLE_TIMEOUT : idleTimeUnit.toNanos(idleTime);
+ }
+
+ @Override
+ public void doStart() throws Exception
+ {
+ _lease = ThreadPoolBudget.leaseFrom(getExecutor(), this, _capacity);
+ _count.set(0, 0);
+ super.doStart();
+ }
+
+ @Override
+ public void doStop() throws Exception
+ {
+ if (_lease != null)
+ _lease.close();
+
+ super.doStop();
+
+ // Mark this instance as stopped.
+ int size = _count.getAndSetLo(-1);
+
+ // Offer the STOP task to all waiting reserved threads.
+ for (int i = 0; i < size; ++i)
+ {
+ // Yield to wait for any reserved threads that
+ // have incremented the size but not yet polled.
+ Thread.yield();
+ _queue.offer(STOP);
+ }
+
+ // Interrupt any reserved thread missed the offer,
+ // so they do not wait for the whole idle timeout.
+ _threads.stream()
+ .filter(ReservedThread::isReserved)
+ .map(t -> t._thread)
+ .filter(Objects::nonNull)
+ .forEach(Thread::interrupt);
+ _threads.clear();
+ _count.getAndSetHi(0);
+ }
+
+ @Override
+ public void execute(Runnable task) throws RejectedExecutionException
+ {
+ _executor.execute(task);
+ }
+
+ /**
+ *
Executes the given task if and only if a reserved thread is available.
+ *
+ * @param task the task to run
+ * @return true if and only if a reserved thread was available and has been assigned the task to run.
+ */
+ @Override
+ public boolean tryExecute(Runnable task)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} tryExecute {}", this, task);
+ if (task == null)
+ return false;
+
+ // Offer will only succeed if there is a reserved thread waiting
+ boolean offered = _queue.offer(task);
+
+ // If the offer succeeded we need to reduce the size, unless it is set to -1 in the meantime
+ int size = _count.getLo();
+ while (offered && size > 0 && !_count.compareAndSetLo(size, --size))
+ size = _count.getLo();
+
+ // If size is 0 and we are not stopping, start a new reserved thread
+ if (size == 0 && task != STOP)
+ startReservedThread();
+
+ return offered;
+ }
+
+ private void startReservedThread()
+ {
+ while (true)
+ {
+ long count = _count.get();
+ int pending = getHi(count);
+ int size = getLo(count);
+ if (size < 0 || pending + size >= _capacity)
+ return;
+ if (size == 0)
+ _lastEmptyNanoTime.set(NanoTime.now());
+ if (!_count.compareAndSet(count, pending + 1, size))
+ continue;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} startReservedThread p={}", this, pending + 1);
+ try
+ {
+ ReservedThread thread = new ReservedThread();
+ _threads.add(thread);
+ _executor.execute(thread);
+ }
+ catch (Throwable e)
+ {
+ _count.add(-1, 0);
+ if (LOG.isDebugEnabled())
+ LOG.debug("ignored", e);
+ }
+ return;
+ }
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ Dumpable.dumpObjects(out, indent, this,
+ new DumpableCollection("threads",
+ _threads.stream()
+ .filter(ReservedThread::isReserved)
+ .collect(Collectors.toList())));
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{reserved=%d/%d,pending=%d}",
+ getClass().getSimpleName(),
+ hashCode(),
+ _count.getLo(),
+ _capacity,
+ _count.getHi());
+ }
+
+ private enum State
+ {
+ PENDING,
+ RESERVED,
+ RUNNING,
+ IDLE,
+ STOPPED
+ }
+
+ private class ReservedThread implements Runnable
+ {
+ // The state and thread are kept only for dumping
+ private volatile State _state = State.PENDING;
+ private volatile Thread _thread;
+
+ private boolean isReserved()
+ {
+ return _state == State.RESERVED;
+ }
+
+ private Runnable reservedWait()
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} waiting {}", this, ReservedThreadExecutorSyncQueue.this);
+
+ // Keep waiting until stopped, tasked or idle
+ while (_count.getLo() >= 0)
+ {
+ try
+ {
+ // Always poll at some period as safety to ensure we don't poll forever.
+ Runnable task = _queue.poll(_idleTimeNanos, NANOSECONDS);
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} task={} {}", this, task, ReservedThreadExecutorSyncQueue.this);
+ if (task != null)
+ return task;
+
+ // we have idled out
+ int size = _count.getLo();
+ // decrement size if we have not also been stopped.
+ while (size > 0)
+ {
+ if (_count.compareAndSetLo(size, --size))
+ break;
+ size = _count.getLo();
+ }
+ _state = size >= 0 ? State.IDLE : State.STOPPED;
+ return STOP;
+ }
+ catch (InterruptedException e)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("ignored", e);
+ }
+ }
+ _state = State.STOPPED;
+ return STOP;
+ }
+
+ @Override
+ public void run()
+ {
+ _thread = Thread.currentThread();
+ try
+ {
+ while (true)
+ {
+ long count = _count.get();
+
+ // reduce pending if this thread was pending
+ int pending = getHi(count) - (_state == State.PENDING ? 1 : 0);
+ int size = getLo(count);
+
+ State next;
+ if (size < 0 || size >= _capacity)
+ {
+ // The executor has stopped or this thread is excess to capacity
+ next = State.STOPPED;
+ }
+ else
+ {
+ long now = NanoTime.now();
+ long lastEmpty = _lastEmptyNanoTime.get();
+ if (size > 0 && _idleTimeNanos < NanoTime.elapsed(lastEmpty, now) && _lastEmptyNanoTime.compareAndSet(lastEmpty, now))
+ {
+ // it has been too long since we hit zero reserved threads, so are "busy" idle
+ next = State.IDLE;
+ }
+ else
+ {
+ // We will become a reserved thread if we can update the count below.
+ next = State.RESERVED;
+ size++;
+ }
+ }
+
+ // Update count for pending and size
+ if (!_count.compareAndSet(count, pending, size))
+ continue;
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} was={} next={} size={}+{} capacity={}", this, _state, next, pending, size, _capacity);
+ _state = next;
+ if (next != State.RESERVED)
+ break;
+
+ // We are reserved whilst we are waiting for an offered _task.
+ Runnable task = reservedWait();
+
+ // Is the task the STOP poison pill?
+ if (task == STOP)
+ break;
+
+ // Run the task
+ try
+ {
+ _state = State.RUNNING;
+ task.run();
+ }
+ catch (Throwable e)
+ {
+ LOG.warn("Unable to run task", e);
+ }
+ finally
+ {
+ // Clear any interrupted status.
+ Thread.interrupted();
+ }
+ }
+ }
+ finally
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{} exited {}", this, ReservedThreadExecutorSyncQueue.this);
+ _threads.remove(this);
+ _thread = null;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{%s,thread=%s}",
+ getClass().getSimpleName(),
+ hashCode(),
+ _state,
+ _thread);
+ }
+ }
+}
diff --git a/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadPoolBenchmark.java b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadPoolBenchmark.java
index 0a123ee57a66..61db1d0830ec 100644
--- a/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadPoolBenchmark.java
+++ b/tests/jetty-jmh/src/main/java/org/eclipse/jetty/util/thread/jmh/ReservedThreadPoolBenchmark.java
@@ -13,12 +13,12 @@
package org.eclipse.jetty.util.thread.jmh;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.LongAdder;
+import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.jetty.util.thread.ReservedThreadExecutor;
import org.eclipse.jetty.util.thread.TryExecutor;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
@@ -38,23 +38,27 @@
import org.openjdk.jmh.runner.options.OptionsBuilder;
@State(Scope.Benchmark)
-@Warmup(iterations = 3, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
-@Measurement(iterations = 3, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
+@Warmup(iterations = 10, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 10, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
public class ReservedThreadPoolBenchmark
{
public enum Type
{
- RTP
+ RTE_EXCH, RTE_SEMA, RTE_Q
}
- @Param({"RTP"})
+ @Param({"RTE_EXCH", "RTE_SEMA", "RTE_Q"})
Type type;
- @Param({"0", "8", "32"})
+ @Param({"16"})
int size;
QueuedThreadPool qtp;
TryExecutor pool;
+ LongAdder jobs = new LongAdder();
+ LongAdder complete = new LongAdder();
+ LongAdder hit = new LongAdder();
+ LongAdder miss = new LongAdder();
@Setup // (Level.Iteration)
public void buildPool()
@@ -62,10 +66,24 @@ public void buildPool()
qtp = new QueuedThreadPool();
switch (type)
{
- case RTP:
+ case RTE_EXCH:
{
- ReservedThreadExecutor pool = new ReservedThreadExecutor(qtp, size);
- pool.setIdleTimeout(1, TimeUnit.SECONDS);
+ ReservedThreadExecutorExchanger pool = new ReservedThreadExecutorExchanger(qtp, size);
+ pool.setIdleTimeout(5, TimeUnit.SECONDS);
+ this.pool = pool;
+ break;
+ }
+ case RTE_Q:
+ {
+ ReservedThreadExecutorSyncQueue pool = new ReservedThreadExecutorSyncQueue(qtp, size);
+ pool.setIdleTimeout(5, TimeUnit.SECONDS);
+ this.pool = pool;
+ break;
+ }
+ case RTE_SEMA:
+ {
+ ReservedThreadExecutorSemaphore pool = new ReservedThreadExecutorSemaphore(qtp, size);
+ pool.setIdleTimeout(5, TimeUnit.SECONDS);
this.pool = pool;
break;
}
@@ -77,24 +95,39 @@ public void buildPool()
@TearDown // (Level.Iteration)
public void shutdownPool()
{
+ System.err.println("\nShutdown ...");
+ long startSpin = System.nanoTime();
+ while (complete.longValue() < jobs.longValue())
+ {
+ if (NanoTime.secondsSince(startSpin) > 5)
+ {
+ System.err.printf("FAILED %d < %d\n".formatted(complete.longValue(), jobs.longValue()));
+ break;
+ }
+ Thread.onSpinWait();
+ }
+ System.err.println("Stopping ...");
LifeCycle.stop(pool);
LifeCycle.stop(qtp);
pool = null;
qtp = null;
+ System.err.println("Stopped");
+ long hits = hit.sum();
+ System.err.printf("hit:miss = %.1f%% (%d:%d)", 100.0D * hits / (hits + miss.sum()), hits, miss.sum());
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@Threads(1)
- public void testFew() throws Exception
+ public void test001Threads() throws Exception
{
doJob();
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
- @Threads(8)
- public void testSome() throws Exception
+ @Threads(32)
+ public void test032Threads() throws Exception
{
doJob();
}
@@ -102,25 +135,29 @@ public void testSome() throws Exception
@Benchmark
@BenchmarkMode(Mode.Throughput)
@Threads(200)
- public void testMany() throws Exception
+ public void test200Threads() throws Exception
{
doJob();
}
- void doJob() throws Exception
+ void doJob()
{
- CountDownLatch latch = new CountDownLatch(1);
+ jobs.increment();
Runnable task = () ->
{
- Blackhole.consumeCPU(1);
- Thread.yield();
- Blackhole.consumeCPU(1);
- latch.countDown();
- Blackhole.consumeCPU(1);
+ Blackhole.consumeCPU(2);
+ complete.increment();
};
- if (!pool.tryExecute(task))
+ if (pool.tryExecute(task))
+ {
+ hit.increment();
+ }
+ else
+ {
+ miss.increment();
qtp.execute(task);
- latch.await();
+ }
+ // We don't wait for the job to complete here, as we want to measure the speed of dispatch, not execution latency
}
public static void main(String[] args) throws RunnerException