Jetty 12 graceful contexts (#9867)
Removed all shutdown mechanisms from ContextHandler Fixed GracefulHandler --------- Signed-off-by: gregw <gregw@webtide.com> Signed-off-by: Ludovic Orban <lorban@bitronix.be> Co-authored-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
parent
18a0738923
commit
c0de62b2c6
|
@ -25,7 +25,6 @@ import java.util.EventListener;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
@ -54,7 +53,6 @@ import org.eclipse.jetty.util.URIUtil;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
import org.eclipse.jetty.util.component.ClassLoaderDump;
|
import org.eclipse.jetty.util.component.ClassLoaderDump;
|
||||||
import org.eclipse.jetty.util.component.Dumpable;
|
import org.eclipse.jetty.util.component.Dumpable;
|
||||||
import org.eclipse.jetty.util.component.Graceful;
|
|
||||||
import org.eclipse.jetty.util.component.LifeCycle;
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
import org.eclipse.jetty.util.resource.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||||
|
@ -63,14 +61,8 @@ import org.eclipse.jetty.util.thread.Invocable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class ContextHandler extends Handler.Wrapper implements Attributes, Graceful, AliasCheck
|
public class ContextHandler extends Handler.Wrapper implements Attributes, AliasCheck
|
||||||
{
|
{
|
||||||
// TODO where should the alias checking go?
|
|
||||||
// TODO add protected paths to ServletContextHandler?
|
|
||||||
// TODO what about ObjectFactory stuff
|
|
||||||
// TODO what about a Context logger?
|
|
||||||
// TODO init param stuff to ServletContextHandler
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ContextHandler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ContextHandler.class);
|
||||||
private static final ThreadLocal<Context> __context = new ThreadLocal<>();
|
private static final ThreadLocal<Context> __context = new ThreadLocal<>();
|
||||||
|
|
||||||
|
@ -147,10 +139,9 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
|
||||||
public enum Availability
|
public enum Availability
|
||||||
{
|
{
|
||||||
STOPPED, // stopped and can't be made unavailable nor shutdown
|
STOPPED, // stopped and can't be made unavailable nor shutdown
|
||||||
STARTING, // starting inside of doStart. It may go to any of the next states.
|
STARTING, // starting inside doStart. It may go to any of the next states.
|
||||||
AVAILABLE, // running normally
|
AVAILABLE, // running normally
|
||||||
UNAVAILABLE, // Either a startup error or explicit call to setAvailable(false)
|
UNAVAILABLE, // Either a startup error or explicit call to setAvailable(false)
|
||||||
SHUTDOWN, // graceful shutdown
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -583,29 +574,6 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if this context is shutting down
|
|
||||||
*/
|
|
||||||
@ManagedAttribute("true for graceful shutdown, which allows existing requests to complete")
|
|
||||||
public boolean isShutdown()
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
|
|
||||||
* requests can complete, but no new requests are accepted.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<Void> shutdown()
|
|
||||||
{
|
|
||||||
// TODO
|
|
||||||
CompletableFuture<Void> completableFuture = new CompletableFuture<>();
|
|
||||||
completableFuture.complete(null);
|
|
||||||
return completableFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return false if this context is unavailable (sends 503)
|
* @return false if this context is unavailable (sends 503)
|
||||||
*/
|
*/
|
||||||
|
@ -651,15 +619,16 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
|
||||||
Availability availability = _availability.get();
|
Availability availability = _availability.get();
|
||||||
switch (availability)
|
switch (availability)
|
||||||
{
|
{
|
||||||
case STARTING:
|
case STARTING, AVAILABLE ->
|
||||||
case AVAILABLE:
|
{
|
||||||
if (!_availability.compareAndSet(availability, Availability.UNAVAILABLE))
|
if (_availability.compareAndSet(availability, Availability.UNAVAILABLE))
|
||||||
continue;
|
return;
|
||||||
break;
|
}
|
||||||
default:
|
default ->
|
||||||
break;
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1160,14 +1129,6 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
|
||||||
return (H)ContextHandler.this;
|
return (H)ContextHandler.this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getAttribute(String name)
|
|
||||||
{
|
|
||||||
// TODO the Attributes.Layer is a little different to previous
|
|
||||||
// behaviour. We need to verify if that is OK
|
|
||||||
return super.getAttribute(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Request.Handler getErrorHandler()
|
public Request.Handler getErrorHandler()
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,6 +21,8 @@ import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.Response;
|
import org.eclipse.jetty.server.Response;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.CountingCallback;
|
||||||
|
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
import org.eclipse.jetty.util.component.Graceful;
|
import org.eclipse.jetty.util.component.Graceful;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -32,17 +34,17 @@ public class GracefulHandler extends Handler.Wrapper implements Graceful
|
||||||
{
|
{
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GracefulHandler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(GracefulHandler.class);
|
||||||
|
|
||||||
private final LongAdder dispatchedStats = new LongAdder();
|
private final LongAdder _requests = new LongAdder();
|
||||||
private final Shutdown shutdown;
|
private final Shutdown _shutdown;
|
||||||
|
|
||||||
public GracefulHandler()
|
public GracefulHandler()
|
||||||
{
|
{
|
||||||
shutdown = new Shutdown(this)
|
_shutdown = new Shutdown(this)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public boolean isShutdownDone()
|
public boolean isShutdownDone()
|
||||||
{
|
{
|
||||||
long count = dispatchedStats.sum();
|
long count = getCurrentRequestCount();
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("isShutdownDone: count {}", count);
|
LOG.debug("isShutdownDone: count {}", count);
|
||||||
return count == 0;
|
return count == 0;
|
||||||
|
@ -50,6 +52,12 @@ public class GracefulHandler extends Handler.Wrapper implements Graceful
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ManagedAttribute("number of requests being currently handled")
|
||||||
|
public long getCurrentRequestCount()
|
||||||
|
{
|
||||||
|
return _requests.sum();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag indicating that Graceful shutdown has been initiated.
|
* Flag indicating that Graceful shutdown has been initiated.
|
||||||
*
|
*
|
||||||
|
@ -59,7 +67,7 @@ public class GracefulHandler extends Handler.Wrapper implements Graceful
|
||||||
@Override
|
@Override
|
||||||
public boolean isShutdown()
|
public boolean isShutdown()
|
||||||
{
|
{
|
||||||
return shutdown.isShutdown();
|
return _shutdown.isShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -86,18 +94,18 @@ public class GracefulHandler extends Handler.Wrapper implements Graceful
|
||||||
{
|
{
|
||||||
boolean handled = super.handle(request, response, shutdownCallback);
|
boolean handled = super.handle(request, response, shutdownCallback);
|
||||||
if (!handled)
|
if (!handled)
|
||||||
shutdownCallback.decrement();
|
shutdownCallback.completed();
|
||||||
return handled;
|
return handled;
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
{
|
{
|
||||||
shutdownCallback.decrement();
|
Response.writeError(request, response, shutdownCallback, t);
|
||||||
throw t;
|
return true;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (isShutdown())
|
if (isShutdown())
|
||||||
shutdown.check();
|
_shutdown.check();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,43 +114,28 @@ public class GracefulHandler extends Handler.Wrapper implements Graceful
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Shutdown requested");
|
LOG.debug("Shutdown requested");
|
||||||
return shutdown.shutdown();
|
return _shutdown.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ShutdownTrackingCallback extends Callback.Nested
|
private class ShutdownTrackingCallback extends CountingCallback
|
||||||
{
|
{
|
||||||
final Request request;
|
final Request request;
|
||||||
final Response response;
|
final Response response;
|
||||||
|
|
||||||
public ShutdownTrackingCallback(Request request, Response response, Callback callback)
|
public ShutdownTrackingCallback(Request request, Response response, Callback callback)
|
||||||
{
|
{
|
||||||
super(callback);
|
super(callback, 1);
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.response = response;
|
this.response = response;
|
||||||
dispatchedStats.increment();
|
_requests.increment();
|
||||||
}
|
|
||||||
|
|
||||||
public void decrement()
|
|
||||||
{
|
|
||||||
dispatchedStats.decrement();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void failed(Throwable x)
|
public void completed()
|
||||||
{
|
{
|
||||||
decrement();
|
_requests.decrement();
|
||||||
super.failed(x);
|
|
||||||
if (isShutdown())
|
if (isShutdown())
|
||||||
shutdown.check();
|
_shutdown.check();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void succeeded()
|
|
||||||
{
|
|
||||||
decrement();
|
|
||||||
super.succeeded();
|
|
||||||
if (isShutdown())
|
|
||||||
shutdown.check();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,12 +52,11 @@ public class GracefulHandlerTest
|
||||||
{
|
{
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GracefulHandlerTest.class);
|
private static final Logger LOG = LoggerFactory.getLogger(GracefulHandlerTest.class);
|
||||||
private Server server;
|
private Server server;
|
||||||
private ServerConnector connector;
|
|
||||||
|
|
||||||
public Server createServer(Handler handler) throws Exception
|
public Server createServer(Handler handler) throws Exception
|
||||||
{
|
{
|
||||||
server = new Server();
|
server = new Server();
|
||||||
connector = new ServerConnector(server, 1, 1);
|
ServerConnector connector = new ServerConnector(server, 1, 1);
|
||||||
connector.setIdleTimeout(10000);
|
connector.setIdleTimeout(10000);
|
||||||
connector.setShutdownIdleTimeout(1000);
|
connector.setShutdownIdleTimeout(1000);
|
||||||
connector.setPort(0);
|
connector.setPort(0);
|
||||||
|
|
|
@ -19,20 +19,27 @@ import java.io.Writer;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.awaitility.Awaitility;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http.HttpTester;
|
||||||
import org.eclipse.jetty.http.HttpURI;
|
import org.eclipse.jetty.http.HttpURI;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.http.MetaData;
|
import org.eclipse.jetty.http.MetaData;
|
||||||
import org.eclipse.jetty.http.MimeTypes;
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.io.Content;
|
import org.eclipse.jetty.io.Content;
|
||||||
|
import org.eclipse.jetty.io.QuietException;
|
||||||
import org.eclipse.jetty.logging.StacklessLogging;
|
import org.eclipse.jetty.logging.StacklessLogging;
|
||||||
import org.eclipse.jetty.server.ConnectionMetaData;
|
import org.eclipse.jetty.server.ConnectionMetaData;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
@ -40,6 +47,7 @@ import org.eclipse.jetty.server.Context;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.HttpChannel;
|
import org.eclipse.jetty.server.HttpChannel;
|
||||||
import org.eclipse.jetty.server.HttpStream;
|
import org.eclipse.jetty.server.HttpStream;
|
||||||
|
import org.eclipse.jetty.server.LocalConnector;
|
||||||
import org.eclipse.jetty.server.MockConnectionMetaData;
|
import org.eclipse.jetty.server.MockConnectionMetaData;
|
||||||
import org.eclipse.jetty.server.MockConnector;
|
import org.eclipse.jetty.server.MockConnector;
|
||||||
import org.eclipse.jetty.server.MockHttpStream;
|
import org.eclipse.jetty.server.MockHttpStream;
|
||||||
|
@ -52,6 +60,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
|
import org.eclipse.jetty.util.component.Graceful;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
@ -891,4 +900,165 @@ public class ContextHandlerTest
|
||||||
assertThat(r, sameInstance(request));
|
assertThat(r, sameInstance(request));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGraceful() throws Exception
|
||||||
|
{
|
||||||
|
// This is really just another test of GracefulHandler, but good to check it works inside of ContextHandler
|
||||||
|
|
||||||
|
CountDownLatch latch0 = new CountDownLatch(1);
|
||||||
|
CountDownLatch latch1 = new CountDownLatch(1);
|
||||||
|
|
||||||
|
CountDownLatch requests = new CountDownLatch(7);
|
||||||
|
|
||||||
|
Handler handler = new Handler.Abstract()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||||
|
{
|
||||||
|
requests.countDown();
|
||||||
|
switch (request.getContext().getPathInContext(request.getHttpURI().getCanonicalPath()))
|
||||||
|
{
|
||||||
|
case "/ignore0" ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
latch0.await();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "/ignore1" ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
latch1.await();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "/ok0" ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
latch0.await();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "/ok1" ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
latch1.await();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case "/fail0" ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
latch0.await();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
throw new QuietException.Exception("expected0");
|
||||||
|
}
|
||||||
|
|
||||||
|
case "/fail1" ->
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
latch1.await();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
callback.failed(new QuietException.Exception("expected1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
default ->
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setStatus(HttpStatus.OK_200);
|
||||||
|
callback.succeeded();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
_contextHandler.setHandler(handler);
|
||||||
|
GracefulHandler gracefulHandler = new GracefulHandler();
|
||||||
|
_contextHandler.insertHandler(gracefulHandler);
|
||||||
|
LocalConnector connector = new LocalConnector(_server);
|
||||||
|
_server.addConnector(connector);
|
||||||
|
_server.start();
|
||||||
|
|
||||||
|
HttpTester.Response response = HttpTester.parseResponse(connector.getResponse("GET /ctx/ HTTP/1.0\r\n\r\n"));
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
|
||||||
|
List<LocalConnector.LocalEndPoint> endPoints = new ArrayList<>();
|
||||||
|
for (String target : new String[] {"/ignore", "/ok", "/fail"})
|
||||||
|
{
|
||||||
|
for (int batch = 0; batch <= 1; batch++)
|
||||||
|
{
|
||||||
|
LocalConnector.LocalEndPoint endPoint = connector.executeRequest("GET /ctx%s%d HTTP/1.0\r\n\r\n".formatted(target, batch));
|
||||||
|
endPoints.add(endPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(requests.await(10, TimeUnit.SECONDS));
|
||||||
|
assertThat(gracefulHandler.getCurrentRequestCount(), is(6L));
|
||||||
|
|
||||||
|
CompletableFuture<Void> shutdown = Graceful.shutdown(_contextHandler);
|
||||||
|
assertFalse(shutdown.isDone());
|
||||||
|
assertThat(gracefulHandler.getCurrentRequestCount(), is(6L));
|
||||||
|
|
||||||
|
response = HttpTester.parseResponse(connector.getResponse("GET /ctx/ HTTP/1.0\r\n\r\n"));
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.SERVICE_UNAVAILABLE_503));
|
||||||
|
|
||||||
|
latch0.countDown();
|
||||||
|
|
||||||
|
response = HttpTester.parseResponse(endPoints.get(0).getResponse());
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
|
response = HttpTester.parseResponse(endPoints.get(2).getResponse());
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
response = HttpTester.parseResponse(endPoints.get(4).getResponse());
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
|
||||||
|
|
||||||
|
assertFalse(shutdown.isDone());
|
||||||
|
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> gracefulHandler.getCurrentRequestCount() == 3L);
|
||||||
|
assertThat(gracefulHandler.getCurrentRequestCount(), is(3L));
|
||||||
|
|
||||||
|
latch1.countDown();
|
||||||
|
|
||||||
|
response = HttpTester.parseResponse(endPoints.get(1).getResponse());
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.NOT_FOUND_404));
|
||||||
|
response = HttpTester.parseResponse(endPoints.get(3).getResponse());
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.OK_200));
|
||||||
|
response = HttpTester.parseResponse(endPoints.get(5).getResponse());
|
||||||
|
assertThat(response.getStatus(), is(HttpStatus.INTERNAL_SERVER_ERROR_500));
|
||||||
|
|
||||||
|
shutdown.get(10, TimeUnit.SECONDS);
|
||||||
|
assertTrue(shutdown.isDone());
|
||||||
|
assertThat(gracefulHandler.getCurrentRequestCount(), is(0L));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.util;
|
package org.eclipse.jetty.util;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@ -172,6 +173,12 @@ public interface Callback extends Invocable
|
||||||
{
|
{
|
||||||
return invocationType;
|
return invocationType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "Callback@%x{%s, %s,%s}".formatted(hashCode(), invocationType, success, failure);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,13 +190,7 @@ public interface Callback extends Invocable
|
||||||
*/
|
*/
|
||||||
static Callback from(Runnable completed)
|
static Callback from(Runnable completed)
|
||||||
{
|
{
|
||||||
return new Completing()
|
return from(Invocable.getInvocationType(completed), completed);
|
||||||
{
|
|
||||||
public void completed()
|
|
||||||
{
|
|
||||||
completed.run();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -202,13 +203,25 @@ public interface Callback extends Invocable
|
||||||
*/
|
*/
|
||||||
static Callback from(InvocationType invocationType, Runnable completed)
|
static Callback from(InvocationType invocationType, Runnable completed)
|
||||||
{
|
{
|
||||||
return new Completing(invocationType)
|
return new Completing()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void completed()
|
public void completed()
|
||||||
{
|
{
|
||||||
completed.run();
|
completed.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InvocationType getInvocationType()
|
||||||
|
{
|
||||||
|
return invocationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return "Callback.Completing@%x{%s,%s}".formatted(hashCode(), invocationType, completed);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,81 +322,40 @@ public interface Callback extends Invocable
|
||||||
*/
|
*/
|
||||||
static Callback from(Callback callback1, Callback callback2)
|
static Callback from(Callback callback1, Callback callback2)
|
||||||
{
|
{
|
||||||
return new Callback()
|
return combine(callback1, callback2);
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void succeeded()
|
|
||||||
{
|
|
||||||
callback1.succeeded();
|
|
||||||
callback2.succeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void failed(Throwable x)
|
|
||||||
{
|
|
||||||
callback1.failed(x);
|
|
||||||
callback2.failed(x);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>A Callback implementation that calls the {@link #completed()} method when it either succeeds or fails.</p>
|
* <p>A Callback implementation that calls the {@link #completed()} method when it either succeeds or fails.</p>
|
||||||
*/
|
*/
|
||||||
class Completing implements Callback
|
interface Completing extends Callback
|
||||||
{
|
{
|
||||||
private final InvocationType invocationType;
|
void completed();
|
||||||
|
|
||||||
public Completing()
|
|
||||||
{
|
|
||||||
this(InvocationType.BLOCKING);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Completing(InvocationType invocationType)
|
|
||||||
{
|
|
||||||
this.invocationType = invocationType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void succeeded()
|
default void succeeded()
|
||||||
{
|
{
|
||||||
completed();
|
completed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void failed(Throwable x)
|
default void failed(Throwable x)
|
||||||
{
|
{
|
||||||
completed();
|
completed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public InvocationType getInvocationType()
|
|
||||||
{
|
|
||||||
return invocationType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void completed()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nested Completing Callback that completes after
|
* Nested Completing Callback that completes after
|
||||||
* completing the nested callback
|
* completing the nested callback
|
||||||
*/
|
*/
|
||||||
class Nested extends Completing
|
class Nested implements Completing
|
||||||
{
|
{
|
||||||
private final Callback callback;
|
private final Callback callback;
|
||||||
|
|
||||||
public Nested(Callback callback)
|
public Nested(Callback callback)
|
||||||
{
|
{
|
||||||
super(Invocable.getInvocationType(callback));
|
this.callback = Objects.requireNonNull(callback);
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Nested(Nested nested)
|
|
||||||
{
|
|
||||||
this(nested.callback);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Callback getCallback()
|
public Callback getCallback()
|
||||||
|
@ -391,6 +363,11 @@ public interface Callback extends Invocable
|
||||||
return callback;
|
return callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void completed()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void succeeded()
|
public void succeeded()
|
||||||
{
|
{
|
||||||
|
@ -461,7 +438,7 @@ public interface Callback extends Invocable
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
{
|
{
|
||||||
if (x != t)
|
if (ExceptionUtil.areNotAssociated(x, t))
|
||||||
x.addSuppressed(t);
|
x.addSuppressed(t);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|
|
@ -108,6 +108,11 @@ public interface Graceful
|
||||||
done.complete(null);
|
done.complete(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method can be called after {@link #shutdown()} has been called, but before
|
||||||
|
* {@link #check()} has been called with {@link #isShutdownDone()} having returned
|
||||||
|
* true to cancel the effects of the {@link #shutdown()} call.
|
||||||
|
*/
|
||||||
public void cancel()
|
public void cancel()
|
||||||
{
|
{
|
||||||
CompletableFuture<Void> done = _done.get();
|
CompletableFuture<Void> done = _done.get();
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under the
|
||||||
|
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||||
|
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.util.component;
|
||||||
|
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
public class GracefulShutdownTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testGracefulShutdown() throws Exception
|
||||||
|
{
|
||||||
|
AtomicBoolean isShutdown = new AtomicBoolean();
|
||||||
|
Graceful.Shutdown shutdown = new Graceful.Shutdown("testGracefulShutdown")
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean isShutdownDone()
|
||||||
|
{
|
||||||
|
return isShutdown.get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
assertThat(shutdown.isShutdown(), is(false));
|
||||||
|
|
||||||
|
shutdown.check();
|
||||||
|
assertThat(shutdown.isShutdown(), is(false));
|
||||||
|
|
||||||
|
CompletableFuture<Void> cf = shutdown.shutdown();
|
||||||
|
assertThat(shutdown.isShutdown(), is(true));
|
||||||
|
shutdown.check();
|
||||||
|
assertThat(shutdown.isShutdown(), is(true));
|
||||||
|
|
||||||
|
assertThrows(TimeoutException.class, () -> cf.get(10, TimeUnit.MILLISECONDS));
|
||||||
|
|
||||||
|
isShutdown.set(true);
|
||||||
|
shutdown.check();
|
||||||
|
assertThat(shutdown.isShutdown(), is(true));
|
||||||
|
assertThat(cf.get(), nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGracefulShutdownCancel() throws Exception
|
||||||
|
{
|
||||||
|
AtomicBoolean isShutdown = new AtomicBoolean();
|
||||||
|
Graceful.Shutdown shutdown = new Graceful.Shutdown("testGracefulShutdownCancel")
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean isShutdownDone()
|
||||||
|
{
|
||||||
|
return isShutdown.get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CompletableFuture<Void> cf1 = shutdown.shutdown();
|
||||||
|
shutdown.cancel();
|
||||||
|
assertThat(shutdown.isShutdown(), is(false));
|
||||||
|
assertThrows(CancellationException.class, cf1::get);
|
||||||
|
|
||||||
|
CompletableFuture<Void> cf2 = shutdown.shutdown();
|
||||||
|
assertThat(shutdown.isShutdown(), is(true));
|
||||||
|
isShutdown.set(true);
|
||||||
|
assertThrows(TimeoutException.class, () -> cf2.get(10, TimeUnit.MILLISECONDS));
|
||||||
|
shutdown.check();
|
||||||
|
assertThat(shutdown.isShutdown(), is(true));
|
||||||
|
assertThat(cf2.get(), nullValue());
|
||||||
|
}
|
||||||
|
}
|
|
@ -95,7 +95,6 @@ import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
import org.eclipse.jetty.util.component.Dumpable;
|
import org.eclipse.jetty.util.component.Dumpable;
|
||||||
import org.eclipse.jetty.util.component.DumpableCollection;
|
import org.eclipse.jetty.util.component.DumpableCollection;
|
||||||
import org.eclipse.jetty.util.component.Environment;
|
import org.eclipse.jetty.util.component.Environment;
|
||||||
import org.eclipse.jetty.util.component.Graceful;
|
|
||||||
import org.eclipse.jetty.util.component.LifeCycle;
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
import org.eclipse.jetty.util.resource.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||||
|
@ -120,7 +119,7 @@ import static jakarta.servlet.ServletContext.TEMPDIR;
|
||||||
* cause confusion with {@link ServletContext}.
|
* cause confusion with {@link ServletContext}.
|
||||||
*/
|
*/
|
||||||
@ManagedObject("Servlet Context Handler")
|
@ManagedObject("Servlet Context Handler")
|
||||||
public class ServletContextHandler extends ContextHandler implements Graceful
|
public class ServletContextHandler extends ContextHandler
|
||||||
{
|
{
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ServletContextHandler.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ServletContextHandler.class);
|
||||||
public static final Environment __environment = Environment.ensure("ee10");
|
public static final Environment __environment = Environment.ensure("ee10");
|
||||||
|
|
|
@ -31,7 +31,6 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@ -90,7 +89,6 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||||
import org.eclipse.jetty.util.component.DumpableCollection;
|
import org.eclipse.jetty.util.component.DumpableCollection;
|
||||||
import org.eclipse.jetty.util.component.Environment;
|
import org.eclipse.jetty.util.component.Environment;
|
||||||
import org.eclipse.jetty.util.component.Graceful;
|
|
||||||
import org.eclipse.jetty.util.resource.Resource;
|
import org.eclipse.jetty.util.resource.Resource;
|
||||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||||
import org.eclipse.jetty.util.resource.Resources;
|
import org.eclipse.jetty.util.resource.Resources;
|
||||||
|
@ -121,7 +119,7 @@ import org.slf4j.LoggerFactory;
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
@ManagedObject("EE9 Context")
|
@ManagedObject("EE9 Context")
|
||||||
public class ContextHandler extends ScopedHandler implements Attributes, Graceful, Supplier<Handler>
|
public class ContextHandler extends ScopedHandler implements Attributes, Supplier<Handler>
|
||||||
{
|
{
|
||||||
public static final Environment ENVIRONMENT = Environment.ensure("ee9");
|
public static final Environment ENVIRONMENT = Environment.ensure("ee9");
|
||||||
public static final int SERVLET_MAJOR_VERSION = 5;
|
public static final int SERVLET_MAJOR_VERSION = 5;
|
||||||
|
@ -552,25 +550,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
||||||
return getEventListeners().contains(listener);
|
return getEventListeners().contains(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if this context is shutting down
|
|
||||||
*/
|
|
||||||
@ManagedAttribute("true for graceful shutdown, which allows existing requests to complete")
|
|
||||||
public boolean isShutdown()
|
|
||||||
{
|
|
||||||
return _coreContextHandler.isShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
|
|
||||||
* requests can complete, but no new requests are accepted.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public CompletableFuture<Void> shutdown()
|
|
||||||
{
|
|
||||||
return _coreContextHandler.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return false if this context is unavailable (sends 503)
|
* @return false if this context is unavailable (sends 503)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1530,7 +1530,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
||||||
if (x instanceof HttpException httpException)
|
if (x instanceof HttpException httpException)
|
||||||
{
|
{
|
||||||
MetaData.Response responseMeta = new MetaData.Response(httpException.getCode(), httpException.getReason(), HttpVersion.HTTP_1_1, HttpFields.build().add(HttpFields.CONNECTION_CLOSE), 0);
|
MetaData.Response responseMeta = new MetaData.Response(httpException.getCode(), httpException.getReason(), HttpVersion.HTTP_1_1, HttpFields.build().add(HttpFields.CONNECTION_CLOSE), 0);
|
||||||
send(_request.getMetaData(), responseMeta, null, true, new Nested(this)
|
send(_request.getMetaData(), responseMeta, null, true, new Nested(getCallback())
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void succeeded()
|
public void succeeded()
|
||||||
|
|
Loading…
Reference in New Issue