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.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
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.component.ClassLoaderDump;
|
||||
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.resource.Resource;
|
||||
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.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 ThreadLocal<Context> __context = new ThreadLocal<>();
|
||||
|
||||
|
@ -147,10 +139,9 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
|
|||
public enum Availability
|
||||
{
|
||||
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
|
||||
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)
|
||||
*/
|
||||
|
@ -651,15 +619,16 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
|
|||
Availability availability = _availability.get();
|
||||
switch (availability)
|
||||
{
|
||||
case STARTING:
|
||||
case AVAILABLE:
|
||||
if (!_availability.compareAndSet(availability, Availability.UNAVAILABLE))
|
||||
continue;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case STARTING, AVAILABLE ->
|
||||
{
|
||||
if (_availability.compareAndSet(availability, Availability.UNAVAILABLE))
|
||||
return;
|
||||
}
|
||||
default ->
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1160,14 +1129,6 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
|
|||
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
|
||||
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.Response;
|
||||
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.slf4j.Logger;
|
||||
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 final LongAdder dispatchedStats = new LongAdder();
|
||||
private final Shutdown shutdown;
|
||||
private final LongAdder _requests = new LongAdder();
|
||||
private final Shutdown _shutdown;
|
||||
|
||||
public GracefulHandler()
|
||||
{
|
||||
shutdown = new Shutdown(this)
|
||||
_shutdown = new Shutdown(this)
|
||||
{
|
||||
@Override
|
||||
public boolean isShutdownDone()
|
||||
{
|
||||
long count = dispatchedStats.sum();
|
||||
long count = getCurrentRequestCount();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("isShutdownDone: count {}", count);
|
||||
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.
|
||||
*
|
||||
|
@ -59,7 +67,7 @@ public class GracefulHandler extends Handler.Wrapper implements Graceful
|
|||
@Override
|
||||
public boolean isShutdown()
|
||||
{
|
||||
return shutdown.isShutdown();
|
||||
return _shutdown.isShutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -86,18 +94,18 @@ public class GracefulHandler extends Handler.Wrapper implements Graceful
|
|||
{
|
||||
boolean handled = super.handle(request, response, shutdownCallback);
|
||||
if (!handled)
|
||||
shutdownCallback.decrement();
|
||||
shutdownCallback.completed();
|
||||
return handled;
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
shutdownCallback.decrement();
|
||||
throw t;
|
||||
Response.writeError(request, response, shutdownCallback, t);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (isShutdown())
|
||||
shutdown.check();
|
||||
_shutdown.check();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,43 +114,28 @@ public class GracefulHandler extends Handler.Wrapper implements Graceful
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
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 Response response;
|
||||
|
||||
public ShutdownTrackingCallback(Request request, Response response, Callback callback)
|
||||
{
|
||||
super(callback);
|
||||
super(callback, 1);
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
dispatchedStats.increment();
|
||||
}
|
||||
|
||||
public void decrement()
|
||||
{
|
||||
dispatchedStats.decrement();
|
||||
_requests.increment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
public void completed()
|
||||
{
|
||||
decrement();
|
||||
super.failed(x);
|
||||
_requests.decrement();
|
||||
if (isShutdown())
|
||||
shutdown.check();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
decrement();
|
||||
super.succeeded();
|
||||
if (isShutdown())
|
||||
shutdown.check();
|
||||
_shutdown.check();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,12 +52,11 @@ public class GracefulHandlerTest
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GracefulHandlerTest.class);
|
||||
private Server server;
|
||||
private ServerConnector connector;
|
||||
|
||||
public Server createServer(Handler handler) throws Exception
|
||||
{
|
||||
server = new Server();
|
||||
connector = new ServerConnector(server, 1, 1);
|
||||
ServerConnector connector = new ServerConnector(server, 1, 1);
|
||||
connector.setIdleTimeout(10000);
|
||||
connector.setShutdownIdleTimeout(1000);
|
||||
connector.setPort(0);
|
||||
|
|
|
@ -19,20 +19,27 @@ import java.io.Writer;
|
|||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
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.HttpVersion;
|
||||
import org.eclipse.jetty.http.MetaData;
|
||||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.io.QuietException;
|
||||
import org.eclipse.jetty.logging.StacklessLogging;
|
||||
import org.eclipse.jetty.server.ConnectionMetaData;
|
||||
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.HttpChannel;
|
||||
import org.eclipse.jetty.server.HttpStream;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.MockConnectionMetaData;
|
||||
import org.eclipse.jetty.server.MockConnector;
|
||||
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.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.component.Graceful;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -891,4 +900,165 @@ public class ContextHandlerTest
|
|||
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;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
@ -172,6 +173,12 @@ public interface Callback extends Invocable
|
|||
{
|
||||
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)
|
||||
{
|
||||
return new Completing()
|
||||
{
|
||||
public void completed()
|
||||
{
|
||||
completed.run();
|
||||
}
|
||||
};
|
||||
return from(Invocable.getInvocationType(completed), completed);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,13 +203,25 @@ public interface Callback extends Invocable
|
|||
*/
|
||||
static Callback from(InvocationType invocationType, Runnable completed)
|
||||
{
|
||||
return new Completing(invocationType)
|
||||
return new Completing()
|
||||
{
|
||||
@Override
|
||||
public void completed()
|
||||
{
|
||||
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)
|
||||
{
|
||||
return new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
callback1.succeeded();
|
||||
callback2.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
callback1.failed(x);
|
||||
callback2.failed(x);
|
||||
}
|
||||
};
|
||||
return combine(callback1, callback2);
|
||||
}
|
||||
|
||||
/**
|
||||
* <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;
|
||||
|
||||
public Completing()
|
||||
{
|
||||
this(InvocationType.BLOCKING);
|
||||
}
|
||||
|
||||
public Completing(InvocationType invocationType)
|
||||
{
|
||||
this.invocationType = invocationType;
|
||||
}
|
||||
void completed();
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
default void succeeded()
|
||||
{
|
||||
completed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
default void failed(Throwable x)
|
||||
{
|
||||
completed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
return invocationType;
|
||||
}
|
||||
|
||||
public void completed()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested Completing Callback that completes after
|
||||
* completing the nested callback
|
||||
*/
|
||||
class Nested extends Completing
|
||||
class Nested implements Completing
|
||||
{
|
||||
private final Callback callback;
|
||||
|
||||
public Nested(Callback callback)
|
||||
{
|
||||
super(Invocable.getInvocationType(callback));
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public Nested(Nested nested)
|
||||
{
|
||||
this(nested.callback);
|
||||
this.callback = Objects.requireNonNull(callback);
|
||||
}
|
||||
|
||||
public Callback getCallback()
|
||||
|
@ -391,6 +363,11 @@ public interface Callback extends Invocable
|
|||
return callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void completed()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
|
@ -461,7 +438,7 @@ public interface Callback extends Invocable
|
|||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
if (x != t)
|
||||
if (ExceptionUtil.areNotAssociated(x, t))
|
||||
x.addSuppressed(t);
|
||||
}
|
||||
finally
|
||||
|
|
|
@ -108,6 +108,11 @@ public interface Graceful
|
|||
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()
|
||||
{
|
||||
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.DumpableCollection;
|
||||
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.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
@ -120,7 +119,7 @@ import static jakarta.servlet.ServletContext.TEMPDIR;
|
|||
* cause confusion with {@link ServletContext}.
|
||||
*/
|
||||
@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);
|
||||
public static final Environment __environment = Environment.ensure("ee10");
|
||||
|
|
|
@ -31,7 +31,6 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
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.component.DumpableCollection;
|
||||
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.ResourceFactory;
|
||||
import org.eclipse.jetty.util.resource.Resources;
|
||||
|
@ -121,7 +119,7 @@ import org.slf4j.LoggerFactory;
|
|||
* </p>
|
||||
*/
|
||||
@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 int SERVLET_MAJOR_VERSION = 5;
|
||||
|
@ -552,25 +550,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
|||
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)
|
||||
*/
|
||||
|
|
|
@ -1530,7 +1530,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
|||
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);
|
||||
send(_request.getMetaData(), responseMeta, null, true, new Nested(this)
|
||||
send(_request.getMetaData(), responseMeta, null, true, new Nested(getCallback())
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
|
|
Loading…
Reference in New Issue