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:
commit
f836f87754
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
* @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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue