Jetty HTTP Container native timeout

Signed-off-by: Maxim Nesen <maxim.nesen@oracle.com>
diff --git a/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpContainer.java b/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpContainer.java
index 6a3805f..c555e92 100644
--- a/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpContainer.java
+++ b/containers/jetty-http/src/main/java17/org/glassfish/jersey/jetty/JettyHttpContainer.java
@@ -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;
@@ -91,8 +92,6 @@
      */
     private boolean configSetStatusOverSendError;
 
-    private final ScheduledThreadPoolExecutor timeoutScheduler;
-
     /**
      * Referencing factory for Jetty request.
      */
@@ -141,7 +140,7 @@
     @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);
@@ -253,37 +252,38 @@
         }
     }
 
-    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() {
@@ -437,20 +437,7 @@
         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();
         }
@@ -458,21 +445,6 @@
 
     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.
      *
@@ -480,7 +452,6 @@
      * @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);
     }
 
@@ -490,7 +461,6 @@
      * @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();
@@ -502,7 +472,6 @@
      * @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();