Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jetty HTTP Container native timeout #5611

Merged
merged 1 commit into from
Apr 18, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.eclipse.jetty.security.AuthenticationState;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.Scheduler;
import org.glassfish.jersey.internal.MapPropertiesDelegate;
import org.glassfish.jersey.internal.inject.AbstractBinder;
import org.glassfish.jersey.internal.inject.ReferencingFactory;
Expand Down Expand Up @@ -91,8 +92,6 @@ public final class JettyHttpContainer extends Handler.Abstract implements Contai
*/
private boolean configSetStatusOverSendError;

private final ScheduledThreadPoolExecutor timeoutScheduler;

/**
* Referencing factory for Jetty request.
*/
Expand Down Expand Up @@ -141,7 +140,7 @@ protected void configure() {
@Override
public boolean handle(Request request, Response response, Callback callback) throws Exception {

final ResponseWriter responseWriter = new ResponseWriter(timeoutScheduler, request, response, callback, configSetStatusOverSendError);
final ResponseWriter responseWriter = new ResponseWriter(request, response, callback, configSetStatusOverSendError);
try {
LOGGER.debugLog(LocalizationMessages.CONTAINER_STARTED());
final URI baseUri = getBaseUri(request);
Expand Down Expand Up @@ -253,37 +252,38 @@ private String getBasePath(final Request request) {
}
}

private static final class ResponseWriter implements ContainerResponseWriter {
private static class ResponseWriter implements ContainerResponseWriter {

private final Request request;
private final Response response;
private final Callback callback;
private final boolean configSetStatusOverSendError;
private final long asyncStartTimeNanos;
private final ScheduledExecutorService timeoutScheduler;
private final Scheduler scheduler;
private final ConcurrentLinkedQueue<TimeoutHandler> timeoutHandlerQueue = new ConcurrentLinkedQueue<>();
private ScheduledFuture<?> currentTimerTask;
private Scheduler.Task currentTimerTask;

ResponseWriter(final ScheduledExecutorService timeoutScheduler, final Request request, final Response response,
ResponseWriter(final Request request, final Response response,
final Callback callback, final boolean configSetStatusOverSendError) {
this.timeoutScheduler = timeoutScheduler;
this.request = request;
this.response = response;
this.callback = callback;
this.asyncStartTimeNanos = System.nanoTime();
this.configSetStatusOverSendError = configSetStatusOverSendError;

this.scheduler = request.getComponents().getScheduler();
}

private synchronized void setNewTimeout(long timeOut, TimeUnit timeUnit) {
long timeOutNanos = timeUnit.toNanos(timeOut);
if (currentTimerTask != null) {
// Do not interrupt, see callTimeoutHandlers()
currentTimerTask.cancel(false);
currentTimerTask.cancel();
}
// Use System.nanoTime() as the clock source here, because the returned value is not prone to wall-clock
// drift - unlike System.currentTimeMillis().
long delayNanos = Math.max(asyncStartTimeNanos - System.nanoTime() + timeOutNanos, 0L);
currentTimerTask = timeoutScheduler.schedule(this::callTimeoutHandlers, delayNanos, TimeUnit.NANOSECONDS);
currentTimerTask = scheduler.schedule(this::callTimeoutHandlers, delayNanos, TimeUnit.NANOSECONDS);
}

private void callTimeoutHandlers() {
Expand Down Expand Up @@ -437,50 +437,21 @@ public void doStop() throws Exception {
appHandler.onShutdown(this);
appHandler = null;

timeoutScheduler.shutdown();
boolean needInterrupt = false;
while (true) {
try {
if (timeoutScheduler.awaitTermination(1L, TimeUnit.MINUTES)) {
break;
}
} catch (InterruptedException e) {
if (!needInterrupt) {
needInterrupt = true;
timeoutScheduler.shutdownNow();
}
}
}
if (needInterrupt) {
Thread.currentThread().interrupt();
}
}

private static final AtomicInteger TIMEOUT_HANDLER_ID_GEN = new AtomicInteger();

private static ScheduledThreadPoolExecutor createTimeoutScheduler() {
// Note: creating the thread-pool does not start the core-pool threads.
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, r -> {
Thread t = new Thread(r, "JettyHttpContainer-Timeout-Handler #" + TIMEOUT_HANDLER_ID_GEN.incrementAndGet());
t.setDaemon(true);
return t;
});
// Limit the number of timeout handling threads to a quarter of the number of CPUs, at least 2.
executor.setMaximumPoolSize(Math.max(2, Runtime.getRuntime().availableProcessors() / 4));
executor.allowCoreThreadTimeOut(true);
// Don't Keep timeout handling threads around "forever".
executor.setKeepAliveTime(100, TimeUnit.MILLISECONDS);
return executor;
}

/**
* Create a new Jetty HTTP container.
*
* @param application JAX-RS / Jersey application to be deployed on Jetty HTTP container.
* @param parentContext DI provider specific context with application's registered bindings.
*/
JettyHttpContainer(final Application application, final Object parentContext) {
this.timeoutScheduler = createTimeoutScheduler();
this.appHandler = new ApplicationHandler(application, new JettyBinder(), parentContext);
}

Expand All @@ -490,7 +461,6 @@ private static ScheduledThreadPoolExecutor createTimeoutScheduler() {
* @param application JAX-RS / Jersey application to be deployed on Jetty HTTP container.
*/
JettyHttpContainer(final Application application) {
this.timeoutScheduler = createTimeoutScheduler();
this.appHandler = new ApplicationHandler(application, new JettyBinder());

cacheConfigSetStatusOverSendError();
Expand All @@ -502,7 +472,6 @@ private static ScheduledThreadPoolExecutor createTimeoutScheduler() {
* @param applicationClass JAX-RS / Jersey class of application to be deployed on Jetty HTTP container.
*/
JettyHttpContainer(final Class<? extends Application> applicationClass) {
this.timeoutScheduler = createTimeoutScheduler();
this.appHandler = new ApplicationHandler(applicationClass, new JettyBinder());

cacheConfigSetStatusOverSendError();
Expand Down