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:
Greg Wilkins 2023-06-07 21:05:49 +02:00 committed by GitHub
parent 18a0738923
commit c0de62b2c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 336 additions and 165 deletions

View File

@ -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()
{ {

View File

@ -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();
} }
} }
} }

View File

@ -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);

View File

@ -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));
}
} }

View File

@ -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

View File

@ -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();

View File

@ -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());
}
}

View File

@ -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");

View File

@ -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)
*/ */

View File

@ -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()