Merged branch 'jetty-9.4.x' into 'jetty-10.0.x'.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2021-01-11 11:23:12 +01:00
commit f836f87754
16 changed files with 524 additions and 209 deletions

View File

@ -15,9 +15,12 @@ package org.eclipse.jetty.client;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.eclipse.jetty.client.api.Connection;
@ -40,6 +43,7 @@ public abstract class AbstractConnectionPool extends ContainerLifeCycle implemen
{
private static final Logger LOG = LoggerFactory.getLogger(AbstractConnectionPool.class);
private final AtomicInteger pending = new AtomicInteger();
private final HttpDestination destination;
private final Callback requester;
private final Pool<Connection> pool;
@ -67,12 +71,23 @@ public abstract class AbstractConnectionPool extends ContainerLifeCycle implemen
@Override
public CompletableFuture<Void> preCreateConnections(int connectionCount)
{
CompletableFuture<?>[] futures = new CompletableFuture[connectionCount];
if (LOG.isDebugEnabled())
LOG.debug("Pre-creating connections {}/{}", connectionCount, getMaxConnectionCount());
List<CompletableFuture<?>> futures = new ArrayList<>();
for (int i = 0; i < connectionCount; i++)
{
futures[i] = tryCreateAsync(getMaxConnectionCount());
Pool<Connection>.Entry entry = pool.reserve();
if (entry == null)
break;
pending.incrementAndGet();
Promise.Completable<Connection> future = new FutureConnection(entry);
futures.add(future);
if (LOG.isDebugEnabled())
LOG.debug("Pre-creating connection {}/{} at {}", futures.size(), getMaxConnectionCount(), entry);
destination.newConnection(future);
}
return CompletableFuture.allOf(futures);
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}
protected int getMaxMultiplex()
@ -122,7 +137,7 @@ public abstract class AbstractConnectionPool extends ContainerLifeCycle implemen
@ManagedAttribute(value = "The number of pending connections", readonly = true)
public int getPendingConnectionCount()
{
return pool.getReservedCount();
return pending.get();
}
@Override
@ -158,13 +173,17 @@ public abstract class AbstractConnectionPool extends ContainerLifeCycle implemen
* <p>Returns an idle connection, if available;
* if an idle connection is not available, and the given {@code create} parameter is {@code true}
* or {@link #isMaximizeConnections()} is {@code true},
* then schedules the opening of a new connection, if possible within the configuration of this
* then attempts to open a new connection, if possible within the configuration of this
* connection pool (for example, if it does not exceed the max connection count);
* otherwise returns {@code null}.</p>
* otherwise it attempts to open a new connection, if the number of queued requests is
* greater than the number of pending connections;
* if no connection is available even after the attempts to open, return {@code null}.</p>
* <p>The {@code create} parameter is just a hint: the connection may be created even if
* {@code false}, or may not be created even if {@code true}.</p>
*
* @param create whether to schedule the opening of a connection if no idle connections are available
* @param create a hint to attempt to open a new connection if no idle connections are available
* @return an idle connection or {@code null} if no idle connections are available
* @see #tryCreate(int)
* @see #tryCreate(boolean)
*/
@Override
public Connection acquire(boolean create)
@ -172,75 +191,65 @@ public abstract class AbstractConnectionPool extends ContainerLifeCycle implemen
if (LOG.isDebugEnabled())
LOG.debug("Acquiring create={} on {}", create, this);
Connection connection = activate();
if (connection == null && (create || isMaximizeConnections()))
if (connection == null)
{
tryCreate(destination.getQueuedRequestCount());
tryCreate(create);
connection = activate();
}
return connection;
}
/**
* <p>Schedules the opening of a new connection.</p>
* <p>Whether a new connection is scheduled for opening is determined by the {@code maxPending} parameter:
* if {@code maxPending} is greater than the current number of connections scheduled for opening,
* then this method returns without scheduling the opening of a new connection;
* if {@code maxPending} is negative, a new connection is always scheduled for opening.</p>
* <p>Tries to create a new connection.</p>
* <p>Whether a new connection is created is determined by the {@code create} parameter
* and a count of demand and supply, where the demand is derived from the number of
* queued requests, and the supply is the number of pending connections time the
* {@link #getMaxMultiplex()} factor: if the demand is less than the supply, the
* connection will not be created.</p>
* <p>Since the number of queued requests used to derive the demand may be a stale
* value, it is possible that few more connections than strictly necessary may be
* created, but enough to satisfy the demand.</p>
*
* @param maxPending the max desired number of connections scheduled for opening,
* or a negative number to always trigger the opening of a new connection
* @param create a hint to request to create a connection
*/
protected void tryCreate(int maxPending)
{
tryCreateAsync(maxPending);
}
private CompletableFuture<Void> tryCreateAsync(int maxPending)
protected void tryCreate(boolean create)
{
int connectionCount = getConnectionCount();
if (LOG.isDebugEnabled())
LOG.debug("Try creating connection {}/{} with {}/{} pending", connectionCount, getMaxConnectionCount(), getPendingConnectionCount(), maxPending);
LOG.debug("Try creating connection {}/{} with {} pending", connectionCount, getMaxConnectionCount(), getPendingConnectionCount());
Pool<Connection>.Entry entry = pool.reserve(maxPending);
// If we have already pending sufficient multiplexed connections, then do not create another.
int multiplexed = getMaxMultiplex();
while (true)
{
int pending = this.pending.get();
int supply = pending * multiplexed;
int demand = destination.getQueuedRequestCount() + (create ? 1 : 0);
boolean tryCreate = isMaximizeConnections() || supply < demand;
if (LOG.isDebugEnabled())
LOG.debug("Try creating({}) connection, pending/demand/supply: {}/{}/{}, result={}", create, pending, demand, supply, tryCreate);
if (!tryCreate)
return;
if (this.pending.compareAndSet(pending, pending + 1))
break;
}
// Create the connection.
Pool<Connection>.Entry entry = pool.reserve();
if (entry == null)
return CompletableFuture.completedFuture(null);
{
pending.decrementAndGet();
return;
}
if (LOG.isDebugEnabled())
LOG.debug("Creating connection {}/{}", connectionCount, getMaxConnectionCount());
CompletableFuture<Void> future = new CompletableFuture<>();
destination.newConnection(new Promise<>()
{
@Override
public void succeeded(Connection connection)
{
if (LOG.isDebugEnabled())
LOG.debug("Connection {}/{} creation succeeded {}", connectionCount, getMaxConnectionCount(), connection);
if (!(connection instanceof Attachable))
{
failed(new IllegalArgumentException("Invalid connection object: " + connection));
return;
}
((Attachable)connection).setAttachment(entry);
onCreated(connection);
entry.enable(connection, false);
idle(connection, false);
future.complete(null);
proceed();
}
@Override
public void failed(Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("Connection {}/{} creation failed", connectionCount, getMaxConnectionCount(), x);
entry.remove();
future.completeExceptionally(x);
requester.failed(x);
}
});
return future;
LOG.debug("Creating connection {}/{} at {}", connectionCount, getMaxConnectionCount(), entry);
Promise<Connection> future = new FutureConnection(entry);
destination.newConnection(future);
}
@Override
@ -421,13 +430,58 @@ public abstract class AbstractConnectionPool extends ContainerLifeCycle implemen
@Override
public String toString()
{
return String.format("%s@%x[c=%d/%d/%d,a=%d,i=%d]",
return String.format("%s@%x[c=%d/%d/%d,a=%d,i=%d,q=%d]",
getClass().getSimpleName(),
hashCode(),
getPendingConnectionCount(),
getConnectionCount(),
getMaxConnectionCount(),
getActiveConnectionCount(),
getIdleConnectionCount());
getIdleConnectionCount(),
destination.getQueuedRequestCount());
}
private class FutureConnection extends Promise.Completable<Connection>
{
private final Pool<Connection>.Entry reserved;
public FutureConnection(Pool<Connection>.Entry reserved)
{
this.reserved = reserved;
}
@Override
public void succeeded(Connection connection)
{
if (LOG.isDebugEnabled())
LOG.debug("Connection creation succeeded {}: {}", reserved, connection);
if (connection instanceof Attachable)
{
((Attachable)connection).setAttachment(reserved);
onCreated(connection);
pending.decrementAndGet();
reserved.enable(connection, false);
idle(connection, false);
complete(null);
proceed();
}
else
{
// reduce pending on failure and if not multiplexing also reduce demand
failed(new IllegalArgumentException("Invalid connection object: " + connection));
}
}
@Override
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);
requester.failed(x);
}
}
}

View File

@ -109,7 +109,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
protected void doStart() throws Exception
{
this.connectionPool = newConnectionPool(client);
addBean(connectionPool);
addBean(connectionPool, true);
super.doStart();
Sweeper sweeper = client.getBean(Sweeper.class);
if (sweeper != null && connectionPool instanceof Sweeper.Sweepable)
@ -296,9 +296,8 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
private void send(boolean create)
{
if (getHttpExchanges().isEmpty())
return;
process(create);
if (!getHttpExchanges().isEmpty())
process(create);
}
private void process(boolean create)
@ -306,20 +305,24 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
// The loop is necessary in case of a new multiplexed connection,
// when a single thread notified of the connection opening must
// process all queued exchanges.
// In other cases looping is a work-stealing optimization.
// It is also necessary when thread T1 cannot acquire a connection
// (for example, it has been stolen by thread T2 and the pool has
// enough pending reservations). T1 returns without doing anything
// and therefore it is T2 that must send both queued requests.
while (true)
{
Connection connection = connectionPool.acquire(create);
if (connection == null)
break;
ProcessResult result = process(connection);
if (result == ProcessResult.FINISH)
boolean proceed = process(connection);
if (proceed)
create = false;
else
break;
create = result == ProcessResult.RESTART;
}
}
private ProcessResult process(Connection connection)
private boolean process(Connection connection)
{
HttpClient client = getHttpClient();
HttpExchange exchange = getHttpExchanges().poll();
@ -335,7 +338,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
LOG.debug("{} is stopping", client);
connection.close();
}
return ProcessResult.FINISH;
return false;
}
else
{
@ -353,9 +356,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
// is created. Aborting the exchange a second time will result in
// a no-operation, so we just abort here to cover that edge case.
exchange.abort(cause);
return getHttpExchanges().size() > 0
? (released ? ProcessResult.CONTINUE : ProcessResult.RESTART)
: ProcessResult.FINISH;
return getQueuedRequestCount() > 0;
}
SendFailure failure = send((IConnection)connection, exchange);
@ -363,7 +364,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
{
// Aggressively send other queued requests
// in case connections are multiplexed.
return getQueuedRequestCount() > 0 ? ProcessResult.CONTINUE : ProcessResult.FINISH;
return getQueuedRequestCount() > 0;
}
if (LOG.isDebugEnabled())
@ -373,10 +374,10 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
// Resend this exchange, likely on another connection,
// and return false to avoid to re-enter this method.
send(exchange);
return ProcessResult.FINISH;
return false;
}
request.abort(failure.failure);
return getHttpExchanges().size() > 0 ? ProcessResult.RESTART : ProcessResult.FINISH;
return getQueuedRequestCount() > 0;
}
}
@ -458,7 +459,7 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
// Process queued requests that may be waiting.
// We may create a connection that is not
// needed, but it will eventually idle timeout.
process(true);
send(true);
}
return removed;
}
@ -515,8 +516,8 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
getOrigin(),
hashCode(),
proxy == null ? "" : "(via " + proxy + ")",
exchanges.size(),
connectionPool);
getQueuedRequestCount(),
getConnectionPool());
}
@FunctionalInterface
@ -590,9 +591,4 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
}
}
}
private enum ProcessResult
{
RESTART, CONTINUE, FINISH
}
}

View File

@ -22,8 +22,8 @@ public class ConnectionPoolHelper
return connectionPool.acquire(create);
}
public static void tryCreate(AbstractConnectionPool connectionPool, int pending)
public static void tryCreate(AbstractConnectionPool connectionPool)
{
connectionPool.tryCreate(pending);
connectionPool.tryCreate(true);
}
}

View File

@ -18,10 +18,12 @@ import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -243,9 +245,12 @@ public class ConnectionPoolTest
}
@ParameterizedTest
@MethodSource("pools")
@MethodSource("poolsNoRoundRobin")
public void testQueuedRequestsDontOpenTooManyConnections(ConnectionPoolFactory factory) throws Exception
{
// Round robin connection pool does open a few more
// connections than expected, exclude it from this test.
startServer(new EmptyServerHandler());
ClientConnector clientConnector = new ClientConnector();
@ -301,11 +306,10 @@ public class ConnectionPoolTest
}
@ParameterizedTest
@MethodSource("poolsNoRoundRobin")
public void testConcurrentRequestsDontOpenTooManyConnections(ConnectionPoolFactory factory) throws Exception
@MethodSource("pools")
public void testConcurrentRequestsWithSlowAddressResolver(ConnectionPoolFactory factory) throws Exception
{
// Round robin connection pool does open a few more
// connections than expected, exclude it from this test.
// ConnectionPools may open a few more connections than expected.
startServer(new EmptyServerHandler());
@ -355,9 +359,83 @@ public class ConnectionPoolTest
assertTrue(latch.await(count, TimeUnit.SECONDS));
List<Destination> destinations = client.getDestinations();
assertEquals(1, destinations.size());
}
@ParameterizedTest
@MethodSource("pools")
public void testConcurrentRequestsAllBlockedOnServerWithLargeConnectionPool(ConnectionPoolFactory factory) throws Exception
{
int count = 50;
testConcurrentRequestsAllBlockedOnServer(factory, count, 2 * count);
}
@ParameterizedTest
@MethodSource("pools")
public void testConcurrentRequestsAllBlockedOnServerWithExactConnectionPool(ConnectionPoolFactory factory) throws Exception
{
int count = 50;
testConcurrentRequestsAllBlockedOnServer(factory, count, count);
}
private void testConcurrentRequestsAllBlockedOnServer(ConnectionPoolFactory factory, int count, int maxConnections) throws Exception
{
CyclicBarrier barrier = new CyclicBarrier(count);
QueuedThreadPool serverThreads = new QueuedThreadPool(2 * count);
serverThreads.setName("server");
server = new Server(serverThreads);
connector = new ServerConnector(server);
server.addConnector(connector);
server.setHandler(new EmptyServerHandler()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException
{
try
{
barrier.await();
}
catch (Exception x)
{
throw new ServletException(x);
}
}
});
server.start();
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSelectors(1);
QueuedThreadPool clientThreads = new QueuedThreadPool(2 * count);
clientThreads.setName("client");
HttpClientTransport transport = new HttpClientTransportOverHTTP(clientConnector);
transport.setConnectionPoolFactory(factory.factory);
client = new HttpClient(transport);
client.setExecutor(clientThreads);
client.setMaxConnectionsPerDestination(maxConnections);
client.start();
// Send N requests to the server, all waiting on the server.
// This should open N connections, and the test verifies that
// all N are sent (i.e. the client does not keep any queued).
CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; ++i)
{
int id = i;
clientThreads.execute(() -> client.newRequest("localhost", connector.getLocalPort())
.path("/" + id)
.send(result ->
{
if (result.isSucceeded())
latch.countDown();
}));
}
assertTrue(latch.await(5, TimeUnit.SECONDS), "server requests " + barrier.getNumberWaiting() + "<" + count + " - client: " + client.dump());
List<Destination> destinations = client.getDestinations();
assertEquals(1, destinations.size());
HttpDestination destination = (HttpDestination)destinations.get(0);
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
assertThat(connectionPool.getConnectionCount(), Matchers.lessThanOrEqualTo(count));
assertThat(connectionPool.getConnectionCount(), Matchers.greaterThanOrEqualTo(count));
}
@ParameterizedTest

View File

@ -51,10 +51,12 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
destination.start();
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
Connection connection = connectionPool.acquire(true);
assertNull(connection);
// There are no queued requests, so no connection should be created.
connection = peekIdleConnection(connectionPool, 1, TimeUnit.SECONDS);
assertNull(connection);
if (connection == null)
{
// There are no queued requests, so the newly created connection will be idle.
connection = peekIdleConnection(connectionPool, 5, TimeUnit.SECONDS);
}
assertNotNull(connection);
}
}
@ -70,7 +72,7 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
// Trigger creation of one connection.
ConnectionPoolHelper.tryCreate(connectionPool, 1);
ConnectionPoolHelper.tryCreate(connectionPool);
Connection connection = ConnectionPoolHelper.acquire(connectionPool, false);
if (connection == null)
@ -94,7 +96,7 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
// Trigger creation of one connection.
ConnectionPoolHelper.tryCreate(connectionPool, 1);
ConnectionPoolHelper.tryCreate(connectionPool);
Connection connection1 = connectionPool.acquire(true);
if (connection1 == null)
@ -146,7 +148,7 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
// Trigger creation of one connection.
ConnectionPoolHelper.tryCreate(connectionPool, 1);
ConnectionPoolHelper.tryCreate(connectionPool);
// Make sure we entered idleCreated().
assertTrue(idleLatch.await(5, TimeUnit.SECONDS));
@ -157,7 +159,7 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
assertNull(connection1);
// Trigger creation of a second connection.
ConnectionPoolHelper.tryCreate(connectionPool, 1);
ConnectionPoolHelper.tryCreate(connectionPool);
// Second attempt also returns null because we delayed idleCreated() above.
Connection connection2 = connectionPool.acquire(true);
@ -185,7 +187,7 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
// Trigger creation of one connection.
ConnectionPoolHelper.tryCreate(connectionPool, 1);
ConnectionPoolHelper.tryCreate(connectionPool);
Connection connection1 = connectionPool.acquire(true);
if (connection1 == null)
@ -222,7 +224,7 @@ public class DuplexHttpDestinationTest extends AbstractHttpClientServerTest
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
// Trigger creation of one connection.
ConnectionPoolHelper.tryCreate(connectionPool, 1);
ConnectionPoolHelper.tryCreate(connectionPool);
Connection connection1 = connectionPool.acquire(true);
if (connection1 == null)

View File

@ -657,7 +657,7 @@ public class HttpClientTLSTest
HttpDestination destination = client.resolveDestination(origin);
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
// Trigger the creation of a new connection, but don't use it.
ConnectionPoolHelper.tryCreate(connectionPool, -1);
ConnectionPoolHelper.tryCreate(connectionPool);
// Verify that the connection has been created.
while (true)
{
@ -755,7 +755,7 @@ public class HttpClientTLSTest
HttpDestination destination = client.resolveDestination(origin);
DuplexConnectionPool connectionPool = (DuplexConnectionPool)destination.getConnectionPool();
// Trigger the creation of a new connection, but don't use it.
ConnectionPoolHelper.tryCreate(connectionPool, -1);
ConnectionPoolHelper.tryCreate(connectionPool);
// Verify that the connection has been created.
while (true)
{

View File

@ -55,12 +55,14 @@ public class ValidatingConnectionPoolTest extends AbstractHttpClientServerTest
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(200, response.getStatus());
// The second request should be sent after the validating timeout.
response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(200, response.getStatus());
}
@ -95,6 +97,7 @@ public class ValidatingConnectionPoolTest extends AbstractHttpClientServerTest
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.path("/redirect")
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(200, response.getStatus());
}

View File

@ -289,7 +289,7 @@ This allows for some complex hierarchies of configuration details.
--download=<http-uri>|<location>::
If the file does not exist at the given location, download it from the given http URI.
Note: location is always relative to `${jetty.base}`.
You might need to escape the slash "\|" to use this on some environments.
You might need to escape the pipe "\|" to use this on some environments.
maven.repo.uri=[url]::
The url to use to download Maven dependencies.

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.URIUtil;
@ -60,6 +61,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
private static final String OMISSION_SUFFIX = ".omission";
private static final String ALL_METHODS = "*";
private final List<ConstraintMapping> _constraintMappings = new CopyOnWriteArrayList<>();
private final List<ConstraintMapping> _durableConstraintMappings = new CopyOnWriteArrayList<>();
private final Set<String> _roles = new CopyOnWriteArraySet<>();
private final PathMappings<Map<String, RoleInfo>> _constraintRoles = new PathMappings<>();
private boolean _denyUncoveredMethods = false;
@ -255,9 +257,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
return mappings;
}
/**
* @return Returns the constraintMappings.
*/
@Override
public List<ConstraintMapping> getConstraintMappings()
{
@ -304,9 +303,16 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
@Override
public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
{
_constraintMappings.clear();
_constraintMappings.addAll(constraintMappings);
_durableConstraintMappings.clear();
if (isInDurableState())
{
_durableConstraintMappings.addAll(constraintMappings);
}
if (roles == null)
{
roles = new HashSet<>();
@ -327,10 +333,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
if (isStarted())
{
for (ConstraintMapping mapping : _constraintMappings)
{
processConstraintMapping(mapping);
}
_constraintMappings.stream().forEach(m -> processConstraintMapping(m));
}
}
@ -351,6 +354,10 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
public void addConstraintMapping(ConstraintMapping mapping)
{
_constraintMappings.add(mapping);
if (isInDurableState())
_durableConstraintMappings.add(mapping);
if (mapping.getConstraint() != null && mapping.getConstraint().getRoles() != null)
{
//allow for lazy role naming: if a role is named in a security constraint, try and
@ -364,9 +371,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
}
if (isStarted())
{
processConstraintMapping(mapping);
}
}
@Override
@ -392,10 +397,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
protected void doStart() throws Exception
{
_constraintRoles.reset();
if (_constraintMappings != null)
{
_constraintMappings.stream().forEach(this::processConstraintMapping);
}
_constraintMappings.forEach(this::processConstraintMapping);
//Servlet Spec 3.1 pg 147 sec 13.8.4.2 log paths for which there are uncovered http methods
checkPathsWithUncoveredHttpMethods();
@ -408,6 +410,8 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
{
super.doStop();
_constraintRoles.reset();
_constraintMappings.clear();
_constraintMappings.addAll(_durableConstraintMappings);
}
/**
@ -843,4 +847,23 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
}
return methods;
}
/**
* Constraints can be added to the ConstraintSecurityHandler before the
* associated context is started. These constraints should persist across
* a stop/start. Others can be added after the associated context is starting
* (eg by a web.xml/web-fragment.xml, annotation or javax.servlet api call) -
* these should not be persisted across a stop/start as they will be re-added on
* the restart.
*
* @return true if the context with which this ConstraintSecurityHandler
* has not yet started, or if there is no context, the server has not yet started.
*/
private boolean isInDurableState()
{
ContextHandler context = ContextHandler.getContextHandler(null);
Server server = getServer();
return (context == null && server == null) || (context != null && !context.isRunning()) || (context == null && server != null && !server.isRunning());
}
}

View File

@ -59,6 +59,7 @@ import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Password;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -231,6 +232,78 @@ public class ConstraintTest
assertFalse(mappings.get(3).getConstraint().getAuthenticate());
}
/**
* Test that constraint mappings added before the context starts are
* retained, but those that are added after the context starts are not.
*
* @throws Exception
*/
@Test
public void testDurableConstraints() throws Exception
{
List<ConstraintMapping> mappings = _security.getConstraintMappings();
assertThat("before start", getConstraintMappings().size(), Matchers.equalTo(mappings.size()));
_server.start();
mappings = _security.getConstraintMappings();
assertThat("after start", getConstraintMappings().size(), Matchers.equalTo(mappings.size()));
_server.stop();
//After a stop, just the durable mappings are left
mappings = _security.getConstraintMappings();
assertThat("after stop", getConstraintMappings().size(), Matchers.equalTo(mappings.size()));
_server.start();
//Verify the constraints are just the durables
mappings = _security.getConstraintMappings();
assertThat("after restart", getConstraintMappings().size(), Matchers.equalTo(mappings.size()));
//Add a non-durable constraint
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec("/xxxx/*");
Constraint constraint = new Constraint();
constraint.setAuthenticate(false);
constraint.setName("transient");
mapping.setConstraint(constraint);
_security.addConstraintMapping(mapping);
mappings = _security.getConstraintMappings();
assertThat("after addition", getConstraintMappings().size() + 1, Matchers.equalTo(mappings.size()));
_server.stop();
_server.start();
//After a stop, only the durable mappings remain
mappings = _security.getConstraintMappings();
assertThat("after addition", getConstraintMappings().size(), Matchers.equalTo(mappings.size()));
//test that setConstraintMappings replaces all existing mappings whether durable or not
//test setConstraintMappings in durable state
_server.stop();
_security.setConstraintMappings(Collections.singletonList(mapping));
mappings = _security.getConstraintMappings();
assertThat("after set during stop", 1, Matchers.equalTo(mappings.size()));
_server.start();
mappings = _security.getConstraintMappings();
assertThat("after set after start", 1, Matchers.equalTo(mappings.size()));
//test setConstraintMappings not in durable state
_server.stop();
_server.start();
assertThat("no change after start", 1, Matchers.equalTo(mappings.size()));
_security.setConstraintMappings(getConstraintMappings());
mappings = _security.getConstraintMappings();
assertThat("durables lost", getConstraintMappings().size(), Matchers.equalTo(mappings.size()));
_server.stop();
mappings = _security.getConstraintMappings();
assertThat("no mappings", 0, Matchers.equalTo(mappings.size()));
}
/**
* Equivalent of Servlet Spec 3.1 pg 132, sec 13.4.1.1, Example 13-1
* &#064;ServletSecurity
@ -650,7 +723,7 @@ public class ConstraintTest
@MethodSource("basicScenarios")
public void testBasic(Scenario scenario) throws Exception
{
List<ConstraintMapping> list = new ArrayList<>(_security.getConstraintMappings());
List<ConstraintMapping> list = new ArrayList<>(getConstraintMappings());
Constraint constraint6 = new Constraint();
constraint6.setAuthenticate(true);

View File

@ -242,9 +242,9 @@ public class StartArgs
private void addFile(Module module, String uriLocation)
{
if (module.isSkipFilesValidation())
if (module != null && module.isSkipFilesValidation())
{
StartLog.debug("Not validating %s [files] for %s", module, uriLocation);
StartLog.debug("Not validating module %s [files] for %s", module, uriLocation);
return;
}

View File

@ -194,7 +194,7 @@ Advanced Commands:
Advanced usage, If the file does not exist at the given
location, download it from the given http URI.
Notes: location is always relative to ${jetty.base}.
you might need to escape the slash "\|" to use
you might need to escape the pipe "\|" to use
this on some environments.
maven.repo.uri=[url]
The url to use to download Maven dependencies.

View File

@ -51,7 +51,7 @@ public class MainTest
// cmdLineArgs.add("jetty.http.port=9090");
Main main = new Main();
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[0]));
BaseHome baseHome = main.getBaseHome();
// System.err.println(args);
@ -87,7 +87,7 @@ public class MainTest
cmdLineArgs.add("STOP.WAIT=300");
Main main = new Main();
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[0]));
// System.err.println(args);
// assertEquals(0, args.getEnabledModules().size(), "--stop should not build module tree");
@ -108,7 +108,7 @@ public class MainTest
// cmdLineArgs.add("--debug");
Main main = new Main();
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[0]));
main.listConfig(args);
}
@ -126,8 +126,8 @@ public class MainTest
List<String> cmdLineArgs = new ArrayList<>();
Path homePath = MavenTestingUtils.getTestResourceDir("dist-home").toPath().toRealPath();
cmdLineArgs.add("jetty.home=" + homePath.toString());
cmdLineArgs.add("user.dir=" + homePath.toString());
cmdLineArgs.add("jetty.home=" + homePath);
cmdLineArgs.add("user.dir=" + homePath);
// JVM args
cmdLineArgs.add("--exec");
@ -141,13 +141,8 @@ public class MainTest
assertThat("Extra Jar exists: " + extraJar, Files.exists(extraJar), is(true));
assertThat("Extra Dir exists: " + extraDir, Files.exists(extraDir), is(true));
StringBuilder lib = new StringBuilder();
lib.append("--lib=");
lib.append(extraJar.toString());
lib.append(File.pathSeparator);
lib.append(extraDir.toString());
cmdLineArgs.add(lib.toString());
String lib = "--lib=" + extraJar + File.pathSeparator + extraDir;
cmdLineArgs.add(lib);
// Arbitrary XMLs
cmdLineArgs.add("config.xml");
@ -156,7 +151,7 @@ public class MainTest
Main main = new Main();
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[0]));
BaseHome baseHome = main.getBaseHome();
assertThat("jetty.home", baseHome.getHome(), is(homePath.toString()));
@ -171,8 +166,8 @@ public class MainTest
List<String> cmdLineArgs = new ArrayList<>();
Path homePath = MavenTestingUtils.getTestResourceDir("dist-home").toPath().toRealPath();
cmdLineArgs.add("jetty.home=" + homePath.toString());
cmdLineArgs.add("user.dir=" + homePath.toString());
cmdLineArgs.add("jetty.home=" + homePath);
cmdLineArgs.add("user.dir=" + homePath);
// JVM args
cmdLineArgs.add("--exec");
@ -182,7 +177,7 @@ public class MainTest
Main main = new Main();
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[0]));
BaseHome baseHome = main.getBaseHome();
assertThat("jetty.home", baseHome.getHome(), is(homePath.toString()));
@ -211,7 +206,7 @@ public class MainTest
Main main = new Main();
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[0]));
BaseHome baseHome = main.getBaseHome();
assertThat("jetty.home", baseHome.getHome(), is(homePath.toString()));
@ -226,7 +221,7 @@ public class MainTest
Path distPath = MavenTestingUtils.getTestResourceDir("dist-home").toPath().toRealPath();
Path homePath = MavenTestingUtils.getTargetTestingPath().resolve("dist home with spaces");
IO.copy(distPath.toFile(), homePath.toFile());
homePath.resolve("lib/a library.jar").toFile().createNewFile();
Files.createFile(homePath.resolve("lib/a library.jar"));
List<String> cmdLineArgs = new ArrayList<>();
cmdLineArgs.add("user.dir=" + homePath);
@ -234,7 +229,7 @@ public class MainTest
cmdLineArgs.add("--lib=lib/a library.jar");
Main main = new Main();
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()]));
StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[0]));
BaseHome baseHome = main.getBaseHome();
assertThat("jetty.home", baseHome.getHome(), is(homePath.toString()));

View File

@ -49,7 +49,6 @@ public class Pool<T> implements AutoCloseable, Dumpable
private final List<Entry> entries = new CopyOnWriteArrayList<>();
private final int maxEntries;
private final AtomicInteger pending = new AtomicInteger();
private final StrategyType strategyType;
/*
@ -132,7 +131,7 @@ public class Pool<T> implements AutoCloseable, Dumpable
public int getReservedCount()
{
return pending.get();
return (int)entries.stream().filter(Entry::isReserved).count();
}
public int getIdleCount()
@ -211,7 +210,9 @@ public class Pool<T> implements AutoCloseable, Dumpable
* @return a disabled entry that is contained in the pool,
* or null if the pool is closed or if the pool already contains
* {@link #getMaxEntries()} entries, or the allotment has already been reserved
* @deprecated Use {@link #reserve()} instead
*/
@Deprecated
public Entry reserve(int allotment)
{
try (AutoLock l = lock.lock())
@ -223,12 +224,35 @@ public class Pool<T> implements AutoCloseable, Dumpable
if (space <= 0)
return null;
// The pending count is an AtomicInteger that is only ever incremented here with
// the lock held. Thus the pending count can be reduced immediately after the
// test below, but never incremented. Thus the allotment limit can be enforced.
if (allotment >= 0 && (pending.get() * getMaxMultiplex()) >= allotment)
if (allotment >= 0 && (getReservedCount() * getMaxMultiplex()) >= allotment)
return null;
Entry entry = new Entry();
entries.add(entry);
return entry;
}
}
/**
* Create a new disabled slot into the pool.
* The returned entry must ultimately have the {@link Entry#enable(Object, boolean)}
* method called or be removed via {@link Pool.Entry#remove()} or
* {@link Pool#remove(Pool.Entry)}.
*
* @return a disabled entry that is contained in the pool,
* or null if the pool is closed or if the pool already contains
* {@link #getMaxEntries()} entries
*/
public Entry reserve()
{
try (AutoLock l = lock.lock())
{
if (closed)
return null;
// If we have no space
if (entries.size() >= maxEntries)
return null;
pending.incrementAndGet();
Entry entry = new Entry();
entries.add(entry);
@ -312,7 +336,7 @@ public class Pool<T> implements AutoCloseable, Dumpable
if (entry != null)
return entry;
entry = reserve(-1);
entry = reserve();
if (entry == null)
return null;
@ -427,12 +451,11 @@ public class Pool<T> implements AutoCloseable, Dumpable
@Override
public String toString()
{
return String.format("%s@%x[size=%d closed=%s pending=%d]",
return String.format("%s@%x[size=%d closed=%s]",
getClass().getSimpleName(),
hashCode(),
entries.size(),
closed,
pending.get());
closed);
}
public class Entry
@ -458,7 +481,7 @@ public class Pool<T> implements AutoCloseable, Dumpable
}
/** Enable a reserved entry {@link Entry}.
* An entry returned from the {@link #reserve(int)} method must be enabled with this method,
* An entry returned from the {@link #reserve()} method must be enabled with this method,
* once and only once, before it is usable by the pool.
* The entry may be enabled and not acquired, in which case it is immediately available to be
* acquired, potentially by another thread; or it can be enabled and acquired atomically so that
@ -487,7 +510,7 @@ public class Pool<T> implements AutoCloseable, Dumpable
return false; // Pool has been closed
throw new IllegalStateException("Entry already enabled: " + this);
}
pending.decrementAndGet();
return true;
}
@ -588,11 +611,7 @@ public class Pool<T> implements AutoCloseable, Dumpable
boolean removed = state.compareAndSet(usageCount, -1, multiplexCount, newMultiplexCount);
if (removed)
{
if (usageCount == Integer.MIN_VALUE)
pending.decrementAndGet();
return newMultiplexCount == 0;
}
}
}
@ -601,6 +620,11 @@ public class Pool<T> implements AutoCloseable, Dumpable
return state.getHi() < 0;
}
public boolean isReserved()
{
return state.getHi() == Integer.MIN_VALUE;
}
public boolean isIdle()
{
long encoded = state.get();

View File

@ -45,7 +45,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class PoolTest
{
interface Factory
{
Pool<String> getPool(int maxSize);
@ -66,7 +65,7 @@ public class PoolTest
public void testAcquireRelease(Factory factory)
{
Pool<String> pool = factory.getPool(1);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
assertThat(pool.size(), is(1));
assertThat(pool.getReservedCount(), is(0));
assertThat(pool.getIdleCount(), is(1));
@ -110,7 +109,7 @@ public class PoolTest
public void testRemoveBeforeRelease(Factory factory)
{
Pool<String> pool = factory.getPool(1);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e1 = pool.acquire();
assertThat(pool.remove(e1), is(true));
@ -123,7 +122,7 @@ public class PoolTest
public void testCloseBeforeRelease(Factory factory)
{
Pool<String> pool = factory.getPool(1);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e1 = pool.acquire();
assertThat(pool.size(), is(1));
@ -138,15 +137,72 @@ public class PoolTest
{
Pool<String> pool = factory.getPool(1);
assertThat(pool.size(), is(0));
assertThat(pool.reserve(-1), notNullValue());
assertThat(pool.reserve(), notNullValue());
assertThat(pool.size(), is(1));
assertThat(pool.reserve(-1), nullValue());
assertThat(pool.reserve(), nullValue());
assertThat(pool.size(), is(1));
}
@ParameterizedTest
@MethodSource(value = "strategy")
public void testReserve(Factory factory)
{
Pool<String> pool = factory.getPool(2);
pool.setMaxMultiplex(2);
// Reserve an entry
Pool<String>.Entry e1 = pool.reserve();
assertThat(pool.size(), is(1));
assertThat(pool.getReservedCount(), is(1));
assertThat(pool.getIdleCount(), is(0));
assertThat(pool.getInUseCount(), is(0));
// enable the entry
e1.enable("aaa", false);
assertThat(pool.size(), is(1));
assertThat(pool.getReservedCount(), is(0));
assertThat(pool.getIdleCount(), is(1));
assertThat(pool.getInUseCount(), is(0));
// Reserve another entry
Pool<String>.Entry e2 = pool.reserve();
assertThat(pool.size(), is(2));
assertThat(pool.getReservedCount(), is(1));
assertThat(pool.getIdleCount(), is(1));
assertThat(pool.getInUseCount(), is(0));
// remove the reservation
e2.remove();
assertThat(pool.size(), is(1));
assertThat(pool.getReservedCount(), is(0));
assertThat(pool.getIdleCount(), is(1));
assertThat(pool.getInUseCount(), is(0));
// Reserve another entry
Pool<String>.Entry e3 = pool.reserve();
assertThat(pool.size(), is(2));
assertThat(pool.getReservedCount(), is(1));
assertThat(pool.getIdleCount(), is(1));
assertThat(pool.getInUseCount(), is(0));
// enable and acquire the entry
e3.enable("bbb", true);
assertThat(pool.size(), is(2));
assertThat(pool.getReservedCount(), is(0));
assertThat(pool.getIdleCount(), is(1));
assertThat(pool.getInUseCount(), is(1));
// can't reenable
assertThrows(IllegalStateException.class, () -> e3.enable("xxx", false));
// Can't enable acquired entry
Pool<String>.Entry e = pool.acquire();
assertThrows(IllegalStateException.class, () -> e.enable("xxx", false));
}
@ParameterizedTest
@MethodSource(value = "strategy")
public void testDeprecatedReserve(Factory factory)
{
Pool<String> pool = factory.getPool(2);
@ -203,22 +259,8 @@ public class PoolTest
assertThrows(IllegalStateException.class, () -> e3.enable("xxx", false));
// Can't enable acquired entry
assertThat(pool.acquire(), is(e1));
assertThrows(IllegalStateException.class, () -> e1.enable("xxx", false));
}
@ParameterizedTest
@MethodSource(value = "strategy")
public void testReserveMaxPending(Factory factory)
{
Pool<String> pool = factory.getPool(2);
assertThat(pool.reserve(0), nullValue());
assertThat(pool.reserve(1), notNullValue());
assertThat(pool.reserve(1), nullValue());
assertThat(pool.reserve(2), notNullValue());
assertThat(pool.reserve(2), nullValue());
assertThat(pool.reserve(3), nullValue());
assertThat(pool.reserve(-1), nullValue());
Pool<String>.Entry e = pool.acquire();
assertThrows(IllegalStateException.class, () -> e.enable("xxx", false));
}
@ParameterizedTest
@ -226,9 +268,9 @@ public class PoolTest
public void testReserveNegativeMaxPending(Factory factory)
{
Pool<String> pool = factory.getPool(2);
assertThat(pool.reserve(-1), notNullValue());
assertThat(pool.reserve(-1), notNullValue());
assertThat(pool.reserve(-1), nullValue());
assertThat(pool.reserve(), notNullValue());
assertThat(pool.reserve(), notNullValue());
assertThat(pool.reserve(), nullValue());
}
@ParameterizedTest
@ -236,7 +278,7 @@ public class PoolTest
public void testClose(Factory factory)
{
Pool<String> pool = factory.getPool(1);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
assertThat(pool.isClosed(), is(false));
pool.close();
pool.close();
@ -244,7 +286,7 @@ public class PoolTest
assertThat(pool.isClosed(), is(true));
assertThat(pool.size(), is(0));
assertThat(pool.acquire(), nullValue());
assertThat(pool.reserve(-1), nullValue());
assertThat(pool.reserve(), nullValue());
}
@Test
@ -253,7 +295,7 @@ public class PoolTest
AtomicBoolean closed = new AtomicBoolean();
Pool<Closeable> pool = new Pool<>(FIRST, 1);
Closeable pooled = () -> closed.set(true);
pool.reserve(-1).enable(pooled, false);
pool.reserve().enable(pooled, false);
assertThat(closed.get(), is(false));
pool.close();
assertThat(closed.get(), is(true));
@ -264,7 +306,7 @@ public class PoolTest
public void testRemove(Factory factory)
{
Pool<String> pool = factory.getPool(1);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e1 = pool.acquire();
assertThat(pool.remove(e1), is(true));
@ -282,8 +324,8 @@ public class PoolTest
assertThat(pool.size(), is(0));
assertThat(pool.values().isEmpty(), is(true));
pool.reserve(-1).enable("aaa", false);
pool.reserve(-1).enable("bbb", false);
pool.reserve().enable("aaa", false);
pool.reserve().enable("bbb", false);
assertThat(pool.values().stream().map(Pool.Entry::getPooled).collect(toList()), equalTo(Arrays.asList("aaa", "bbb")));
assertThat(pool.size(), is(2));
}
@ -294,8 +336,8 @@ public class PoolTest
{
Pool<String> pool = factory.getPool(2);
pool.reserve(-1).enable("aaa", false);
pool.reserve(-1).enable("bbb", false);
pool.reserve().enable("aaa", false);
pool.reserve().enable("bbb", false);
assertThat(pool.acquire(), notNullValue());
assertThat(pool.acquire(), notNullValue());
assertThat(pool.acquire(), nullValue());
@ -308,7 +350,7 @@ public class PoolTest
{
Pool<String> pool = factory.getPool(1);
pool.setMaxUsageCount(3);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e1 = pool.acquire();
assertThat(pool.release(e1), is(true));
@ -337,8 +379,8 @@ public class PoolTest
AtomicInteger b = new AtomicInteger();
counts.put("a", a);
counts.put("b", b);
pool.reserve(-1).enable("a", false);
pool.reserve(-1).enable("b", false);
pool.reserve().enable("a", false);
pool.reserve().enable("b", false);
counts.get(pool.acquire().getPooled()).incrementAndGet();
counts.get(pool.acquire().getPooled()).incrementAndGet();
@ -365,7 +407,7 @@ public class PoolTest
{
Pool<String> pool = factory.getPool(1);
pool.setMaxMultiplex(2);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e1 = pool.acquire();
assertThat(e1, notNullValue());
@ -395,7 +437,7 @@ public class PoolTest
{
Pool<String> pool = factory.getPool(1);
pool.setMaxMultiplex(2);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e1 = pool.acquire();
Pool<String>.Entry e2 = pool.acquire();
@ -413,7 +455,7 @@ public class PoolTest
{
Pool<String> pool = factory.getPool(1);
pool.setMaxMultiplex(2);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e1 = pool.acquire();
assertThat(pool.remove(e1), is(true));
@ -426,7 +468,7 @@ public class PoolTest
{
Pool<String> pool = factory.getPool(1);
pool.setMaxMultiplex(2);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e1 = pool.acquire();
Pool<String>.Entry e2 = pool.acquire();
@ -450,7 +492,7 @@ public class PoolTest
public void testReleaseThenRemoveNonEnabledEntry(Factory factory)
{
Pool<String> pool = factory.getPool(1);
Pool<String>.Entry e = pool.reserve(-1);
Pool<String>.Entry e = pool.reserve();
assertThat(pool.size(), is(1));
assertThat(pool.release(e), is(false));
assertThat(pool.size(), is(1));
@ -463,7 +505,7 @@ public class PoolTest
public void testRemoveNonEnabledEntry(Factory factory)
{
Pool<String> pool = factory.getPool(1);
Pool<String>.Entry e = pool.reserve(-1);
Pool<String>.Entry e = pool.reserve();
assertThat(pool.size(), is(1));
assertThat(pool.remove(e), is(true));
assertThat(pool.size(), is(0));
@ -476,7 +518,7 @@ public class PoolTest
Pool<String> pool = factory.getPool(1);
pool.setMaxMultiplex(2);
pool.setMaxUsageCount(3);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e0 = pool.acquire();
@ -497,7 +539,7 @@ public class PoolTest
Pool<String> pool = factory.getPool(1);
pool.setMaxMultiplex(2);
pool.setMaxUsageCount(3);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e0 = pool.acquire();
@ -522,7 +564,7 @@ public class PoolTest
Pool<String> pool = factory.getPool(1);
pool.setMaxMultiplex(2);
pool.setMaxUsageCount(10);
pool.reserve(-1).enable("aaa", false);
pool.reserve().enable("aaa", false);
Pool<String>.Entry e1 = pool.acquire();
assertThat(e1.getUsageCount(), is(1));
@ -538,7 +580,7 @@ public class PoolTest
public void testDynamicMaxUsageCountChangeOverflowMaxInt(Factory factory)
{
Pool<String> pool = factory.getPool(1);
Pool<String>.Entry entry = pool.reserve(-1);
Pool<String>.Entry entry = pool.reserve();
entry.enable("aaa", false);
entry.setUsageCount(Integer.MAX_VALUE);
@ -556,9 +598,9 @@ public class PoolTest
public void testDynamicMaxUsageCountChangeSweep(Factory factory)
{
Pool<String> pool = factory.getPool(2);
Pool<String>.Entry entry1 = pool.reserve(-1);
Pool<String>.Entry entry1 = pool.reserve();
entry1.enable("aaa", false);
Pool<String>.Entry entry2 = pool.reserve(-1);
Pool<String>.Entry entry2 = pool.reserve();
entry2.enable("bbb", false);
Pool<String>.Entry acquired1 = pool.acquire();

View File

@ -42,6 +42,7 @@ import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.DisabledOnOs;
@ -468,6 +469,30 @@ public class DistributionTests extends AbstractJettyHomeTest
}
}
@Test
@Tag("external")
public void testDownload() throws Exception
{
Path jettyBase = Files.createTempDirectory("jetty_base");
String jettyVersion = System.getProperty("jettyVersion");
JettyHomeTester distribution = JettyHomeTester.Builder.newInstance()
.jettyVersion(jettyVersion)
.jettyBase(jettyBase)
.mavenLocalRepository(System.getProperty("mavenRepoPath"))
.build();
String outPath = "etc/maven-metadata.xml";
String[] args1 = {
"--download=https://repo1.maven.org/maven2/org/eclipse/jetty/maven-metadata.xml|" + outPath
};
try (JettyHomeTester.Run run = distribution.start(args1))
{
assertTrue(run.awaitConsoleLogsFor("Base directory was modified", 15, TimeUnit.SECONDS));
Path target = jettyBase.resolve(outPath);
assertTrue(Files.exists(target), "could not create " + target);
}
}
@Test
public void testWebAppWithProxyAndJPMS() throws Exception
{