From 3a68e56c1a7e8008310f1b2e7b85ea55164d88fe Mon Sep 17 00:00:00 2001 From: gregw Date: Sun, 10 Mar 2024 12:14:27 +0100 Subject: [PATCH 01/10] Experiment with fully virtual VirtualThreadPool Virtual threads are used for all threading. --- .../config/etc/jetty-threadpool-hybrid.xml | 43 +++++ .../config/etc/jetty-threadpool-virtual.xml | 30 +--- .../main/config/modules/threadpool-hybrid.mod | 41 +++++ .../config/modules/threadpool-virtual.mod | 25 +-- .../eclipse/jetty/util/VirtualThreads.java | 21 ++- .../jetty/util/thread/QueuedThreadPool.java | 8 +- .../jetty/util/thread/VirtualThreadPool.java | 162 ++++++++++++++++++ .../util/thread/VirtualThreadPoolTest.java | 120 +++++++++++++ .../AdaptiveExecutionStrategyTest.java | 7 +- .../strategy/ExecutionStrategyTest.java | 96 ++++++----- 10 files changed, 452 insertions(+), 101 deletions(-) create mode 100644 jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-hybrid.xml create mode 100644 jetty-core/jetty-server/src/main/config/modules/threadpool-hybrid.mod create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java create mode 100644 jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java rename jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/{ => strategy}/AdaptiveExecutionStrategyTest.java (93%) diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-hybrid.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-hybrid.xml new file mode 100644 index 000000000000..b51e95782fc3 --- /dev/null +++ b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-hybrid.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + -virtual- + + + 0 + + + + + + + + + + + + + + + + + org.eclipse.jetty + + Hybrid Virtual threads are enabled. + + + diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml index 71dc9444ba8d..469bb527f088 100644 --- a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml +++ b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml @@ -2,36 +2,8 @@ - + - - - - - - - - - - - - -virtual- - - - 0 - - - - - - - - - - - - - diff --git a/jetty-core/jetty-server/src/main/config/modules/threadpool-hybrid.mod b/jetty-core/jetty-server/src/main/config/modules/threadpool-hybrid.mod new file mode 100644 index 000000000000..ddc3f10f4bce --- /dev/null +++ b/jetty-core/jetty-server/src/main/config/modules/threadpool-hybrid.mod @@ -0,0 +1,41 @@ +[description] +Enables and configures the Server ThreadPool with support for mix platform and virtual threads in Java 21 or later. + +[depends] +logging + +[provides] +threadpool + +[xml] +etc/jetty-threadpool-hybrid.xml + +[ini-template] +# tag::documentation[] +## Platform threads name prefix. +#jetty.threadPool.namePrefix=qtp + +## Minimum number of pooled threads. +#jetty.threadPool.minThreads=10 + +## Maximum number of pooled threads. +#jetty.threadPool.maxThreads=200 + +## Number of reserved threads (-1 for heuristic). +#jetty.threadPool.reservedThreads=-1 + +## Thread idle timeout (in milliseconds). +#jetty.threadPool.idleTimeout=60000 + +## The max number of idle threads that can be evicted in one idleTimeout period. +#jetty.threadPool.maxEvictCount=1 + +## Whether to output a detailed dump. +#jetty.threadPool.detailedDump=false + +## Virtual threads name prefix. +#jetty.threadPool.virtual.namePrefix=qtp-virtual- + +## Whether virtual threads inherits the values of inheritable thread locals. +#jetty.threadPool.virtual.inheritInheritableThreadLocals=true +# end::documentation[] diff --git a/jetty-core/jetty-server/src/main/config/modules/threadpool-virtual.mod b/jetty-core/jetty-server/src/main/config/modules/threadpool-virtual.mod index 54a2912b4f13..81644499a796 100644 --- a/jetty-core/jetty-server/src/main/config/modules/threadpool-virtual.mod +++ b/jetty-core/jetty-server/src/main/config/modules/threadpool-virtual.mod @@ -13,29 +13,6 @@ etc/jetty-threadpool-virtual.xml [ini-template] # tag::documentation[] ## Platform threads name prefix. -#jetty.threadPool.namePrefix=qtp +#jetty.threadPool.namePrefix=vtp -## Minimum number of pooled threads. -#jetty.threadPool.minThreads=10 - -## Maximum number of pooled threads. -#jetty.threadPool.maxThreads=200 - -## Number of reserved threads (-1 for heuristic). -#jetty.threadPool.reservedThreads=-1 - -## Thread idle timeout (in milliseconds). -#jetty.threadPool.idleTimeout=60000 - -## The max number of idle threads that can be evicted in one idleTimeout period. -#jetty.threadPool.maxEvictCount=1 - -## Whether to output a detailed dump. -#jetty.threadPool.detailedDump=false - -## Virtual threads name prefix. -#jetty.threadPool.virtual.namePrefix=qtp-virtual- - -## Whether virtual threads inherits the values of inheritable thread locals. -#jetty.threadPool.virtual.inheritInheritableThreadLocals=true # end::documentation[] diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java index 8218e6174ba3..43486fac2118 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java @@ -125,7 +125,7 @@ public static boolean isVirtualThread() * @param namePrefix the prefix to use for the name of the virtual threads * @return a virtual threads {@code Executor} that will name the virtual threads according to the provided name prefix. */ - public static Executor getNamedVirtualThreadsExecutor(String namePrefix) + public static ThreadFactoryExecutor getNamedVirtualThreadsExecutor(String namePrefix) { try { @@ -133,7 +133,21 @@ public static Executor getNamedVirtualThreadsExecutor(String namePrefix) Object threadBuilder = Thread.class.getMethod("ofVirtual").invoke(null); threadBuilder = builderClass.getMethod("name", String.class, long.class).invoke(threadBuilder, namePrefix, 0L); ThreadFactory factory = (ThreadFactory)builderClass.getMethod("factory").invoke(threadBuilder); - return (Executor)Executors.class.getMethod("newThreadPerTaskExecutor", ThreadFactory.class).invoke(null, factory); + Executor executor = (Executor)Executors.class.getMethod("newThreadPerTaskExecutor", ThreadFactory.class).invoke(null, factory); + return new ThreadFactoryExecutor() + { + @Override + public void execute(Runnable command) + { + executor.execute(command); + } + + @Override + public Thread newThread(Runnable r) + { + return factory.newThread(r); + } + }; } catch (Throwable x) { @@ -233,4 +247,7 @@ default void setUseVirtualThreads(boolean useVirtualThreads) private VirtualThreads() { } + + public interface ThreadFactoryExecutor extends ThreadFactory, Executor + {} } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java index c4f5d191c818..db93036550c3 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java @@ -77,7 +77,7 @@ * */ @ManagedObject("A thread pool") -public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactory, SizedThreadPool, Dumpable, TryExecutor, VirtualThreads.Configurable +public class QueuedThreadPool extends ContainerLifeCycle implements ThreadPool, ThreadFactory, SizedThreadPool, Dumpable, TryExecutor, VirtualThreads.Configurable { private static final Logger LOG = LoggerFactory.getLogger(QueuedThreadPool.class); private static final Runnable NOOP = () -> @@ -441,7 +441,7 @@ public void setReservedThreads(int reservedThreads) } /** - * @return the name of the this thread pool + * @return the name of this thread pool */ @ManagedAttribute("name of the thread pool") public String getName() @@ -452,7 +452,7 @@ public String getName() /** *

Sets the name of this thread pool, used as a prefix for the thread names.

* - * @param name the name of the this thread pool + * @param name the name of this thread pool */ public void setName(String name) { @@ -827,7 +827,7 @@ public void join() throws InterruptedException while (isStopping()) { - Thread.sleep(1); + Thread.onSpinWait(); } } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java new file mode 100644 index 000000000000..68e3c000e275 --- /dev/null +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java @@ -0,0 +1,162 @@ +// +// ======================================================================== +// 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.thread; + +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadFactory; + +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.VirtualThreads; +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.component.Dumpable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * An implementation of {@link ThreadPool} interface that does not pool, but instead uses {@link VirtualThreads}. + */ +public class VirtualThreadPool extends ContainerLifeCycle implements ThreadFactory, ThreadPool, Dumpable, TryExecutor, VirtualThreads.Configurable +{ + private static final Logger LOG = LoggerFactory.getLogger(VirtualThreadPool.class); + + private String _name = "vtp-%x".formatted(hashCode()); + private VirtualThreads.ThreadFactoryExecutor _virtualExecutor; + private final AutoLock.WithCondition _joinLock = new AutoLock.WithCondition(); + + public VirtualThreadPool() + { + if (VirtualThreads.getDefaultVirtualThreadsExecutor() == null) + throw new IllegalStateException("Virtual Threads not supported"); + } + + /** + * @return the name of this thread pool + */ + @ManagedAttribute("name of the thread pool") + public String getName() + { + return _name; + } + + /** + *

Sets the name of this thread pool, used as a prefix for the thread names.

+ * + * @param name the name of this thread pool + */ + public void setName(String name) + { + if (isRunning()) + throw new IllegalStateException(getState()); + if (StringUtil.isBlank(name)) + throw new IllegalArgumentException("Blank name"); + _name = name; + } + + @Override + protected void doStart() throws Exception + { + _virtualExecutor = VirtualThreads.getNamedVirtualThreadsExecutor(_name); + if (_virtualExecutor == null) + throw new IllegalStateException("Virtual Threads not supported: " + _name); + super.doStart(); + } + + @Override + protected void doStop() throws Exception + { + super.doStop(); + _virtualExecutor = null; + + try (AutoLock.WithCondition l = _joinLock.lock()) + { + l.signalAll(); + } + } + + @Override + public Thread newThread(Runnable r) + { + return _virtualExecutor.newThread(r); + } + + @Override + public Executor getVirtualThreadsExecutor() + { + return _virtualExecutor; + } + + @Override + public void setVirtualThreadsExecutor(Executor executor) + { + throw new UnsupportedOperationException("cannot set VirtualThreadExecutor"); + } + + @Override + public void join() throws InterruptedException + { + try (AutoLock.WithCondition l = _joinLock.lock()) + { + while (isRunning()) + { + l.await(); + } + } + + while (isStopping()) + { + Thread.onSpinWait(); + } + } + + @Override + public int getThreads() + { + return -1; + } + + @Override + public int getIdleThreads() + { + return -1; + } + + @Override + public boolean isLowOnThreads() + { + return false; + } + + @Override + public boolean tryExecute(Runnable task) + { + try + { + _virtualExecutor.execute(task); + return true; + } + catch (RejectedExecutionException e) + { + LOG.warn("tryExecute {} failed", _name, e); + } + return false; + } + + @Override + public void execute(Runnable task) + { + _virtualExecutor.execute(task); + } +} diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java new file mode 100644 index 000000000000..35879f0d9ba7 --- /dev/null +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java @@ -0,0 +1,120 @@ +// +// ======================================================================== +// 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.thread; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledForJreRange; +import org.junit.jupiter.api.condition.JRE; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@DisabledForJreRange(max = JRE.JAVA_20) +public class VirtualThreadPoolTest +{ + @Test + public void testNamed() throws Exception + { + VirtualThreadPool vtp = new VirtualThreadPool(); + assertThat(vtp.getName(), startsWith("vtp")); + vtp.setName("namedV"); + vtp.start(); + + CompletableFuture name = new CompletableFuture<>(); + vtp.execute(() -> name.complete(Thread.currentThread().getName())); + + assertThat(name.get(5, TimeUnit.SECONDS), startsWith("namedV")); + + vtp.stop(); + } + + @Test + public void testJoin() throws Exception + { + VirtualThreadPool vtp = new VirtualThreadPool(); + assertThat(vtp.getName(), startsWith("vtp")); + vtp.start(); + + CountDownLatch running = new CountDownLatch(1); + CountDownLatch joined = new CountDownLatch(1); + + vtp.execute(() -> + { + try + { + running.countDown(); + vtp.join(); + joined.countDown(); + } + catch (Throwable t) + { + throw new RuntimeException(t); + } + }); + + assertTrue(running.await(5, TimeUnit.SECONDS)); + assertThat(joined.getCount(), is(1L)); + vtp.stop(); + assertTrue(joined.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testExecute() throws Exception + { + VirtualThreadPool vtp = new VirtualThreadPool(); + assertThat(vtp.getName(), startsWith("vtp")); + vtp.start(); + + CountDownLatch ran = new CountDownLatch(1); + vtp.execute(ran::countDown); + assertTrue(ran.await(5, TimeUnit.SECONDS)); + vtp.stop(); + } + + @Test + public void testTry() throws Exception + { + VirtualThreadPool vtp = new VirtualThreadPool(); + assertThat(vtp.getName(), startsWith("vtp")); + vtp.start(); + + CountDownLatch ran = new CountDownLatch(1); + assertTrue(vtp.tryExecute(ran::countDown)); + assertTrue(ran.await(5, TimeUnit.SECONDS)); + vtp.stop(); + } + + @Test + public void testThread() throws Exception + { + VirtualThreadPool vtp = new VirtualThreadPool(); + assertThat(vtp.getName(), startsWith("vtp")); + vtp.start(); + + CountDownLatch ran = new CountDownLatch(1); + Thread t = vtp.newThread(ran::countDown); + assertThat(t.getName(), startsWith(vtp.getName())); + assertFalse(ran.await(1, TimeUnit.SECONDS)); + t.start(); + assertTrue(ran.await(5, TimeUnit.SECONDS)); + vtp.stop(); + } +} diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/AdaptiveExecutionStrategyTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/AdaptiveExecutionStrategyTest.java similarity index 93% rename from jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/AdaptiveExecutionStrategyTest.java rename to jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/AdaptiveExecutionStrategyTest.java index f8874ae2de1b..636622b55468 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/AdaptiveExecutionStrategyTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/AdaptiveExecutionStrategyTest.java @@ -11,7 +11,7 @@ // ======================================================================== // -package org.eclipse.jetty.util.thread; +package org.eclipse.jetty.util.thread.strategy; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; @@ -20,7 +20,10 @@ import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jetty.logging.StacklessLogging; -import org.eclipse.jetty.util.thread.strategy.AdaptiveExecutionStrategy; +import org.eclipse.jetty.util.thread.ExecutionStrategy; +import org.eclipse.jetty.util.thread.Invocable; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.ReservedThreadExecutor; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/ExecutionStrategyTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/ExecutionStrategyTest.java index a12c1f6527df..86f0b1cc726e 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/ExecutionStrategyTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/strategy/ExecutionStrategyTest.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; @@ -23,10 +24,15 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; +import org.eclipse.jetty.util.VirtualThreads; +import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.thread.ExecutionStrategy; import org.eclipse.jetty.util.thread.ExecutionStrategy.Producer; +import org.eclipse.jetty.util.thread.ExecutorThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.eclipse.jetty.util.thread.ThreadPool; +import org.eclipse.jetty.util.thread.VirtualThreadPool; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; @@ -39,22 +45,27 @@ public class ExecutionStrategyTest { - public static Stream strategies() + public static Stream pooledStrategies() { return Stream.of( + QueuedThreadPool.class, + ExecutorThreadPool.class, + VirtualThreads.getDefaultVirtualThreadsExecutor() == null ? null : VirtualThreadPool.class) + .filter(Objects::nonNull) + .flatMap(tp -> Stream.of( ProduceExecuteConsume.class, ExecuteProduceConsume.class, - AdaptiveExecutionStrategy.class - ).map(Arguments::of); + AdaptiveExecutionStrategy.class) + .map(s -> Arguments.of(tp, s))); } - QueuedThreadPool _threads = new QueuedThreadPool(20); - List strategies = new ArrayList<>(); + List _lifeCycles = new ArrayList<>(); protected ExecutionStrategy newExecutionStrategy(Class strategyClass, Producer producer, Executor executor) throws Exception { ExecutionStrategy strategy = strategyClass.getDeclaredConstructor(Producer.class, Executor.class).newInstance(producer, executor); - strategies.add(strategy); + _lifeCycles.add(executor); + _lifeCycles.add(strategy); LifeCycle.start(strategy); return strategy; } @@ -62,15 +73,12 @@ protected ExecutionStrategy newExecutionStrategy(Class LifeCycle.stop(strategy)); - _threads.stop(); + _lifeCycles.forEach(LifeCycle::stop); } public abstract static class TestProducer implements Producer @@ -83,9 +91,11 @@ public String toString() } @ParameterizedTest - @MethodSource("strategies") - public void idleTest(Class strategyClass) throws Exception + @MethodSource("pooledStrategies") + public void idleTest(Class threadPoolClass, Class strategyClass) throws Exception { + ThreadPool threadPool = threadPoolClass.getDeclaredConstructor().newInstance(); + LifeCycle.start(threadPool); AtomicInteger count = new AtomicInteger(0); Producer producer = new TestProducer() { @@ -97,16 +107,20 @@ public Runnable produce() } }; - ExecutionStrategy strategy = newExecutionStrategy(strategyClass, producer, _threads); + ExecutionStrategy strategy = newExecutionStrategy(strategyClass, producer, threadPool); strategy.produce(); assertThat(count.get(), greaterThan(0)); + + LifeCycle.stop(threadPool); } @ParameterizedTest - @MethodSource("strategies") - public void simpleTest(Class strategyClass) throws Exception + @MethodSource("pooledStrategies") + public void simpleTest(Class threadPoolClass, Class strategyClass) throws Exception { - final int TASKS = 3 * _threads.getMaxThreads(); + ThreadPool threadPool = threadPoolClass.getDeclaredConstructor().newInstance(); + LifeCycle.start(threadPool); + final int TASKS = 1000; final CountDownLatch latch = new CountDownLatch(TASKS); Producer producer = new TestProducer() { @@ -117,14 +131,14 @@ public Runnable produce() { if (tasks-- > 0) { - return () -> latch.countDown(); + return latch::countDown; } return null; } }; - ExecutionStrategy strategy = newExecutionStrategy(strategyClass, producer, _threads); + ExecutionStrategy strategy = newExecutionStrategy(strategyClass, producer, threadPool); for (int p = 0; latch.getCount() > 0 && p < TASKS; p++) { @@ -136,16 +150,20 @@ public Runnable produce() { // Dump state on failure return String.format("Timed out waiting for latch: %s%ntasks=%d latch=%d%n%s", - strategy, TASKS, latch.getCount(), _threads.dump()); + strategy, TASKS, latch.getCount(), threadPool instanceof Dumpable dumpable ? dumpable.dump() : ""); }); + + LifeCycle.stop(threadPool); } @ParameterizedTest - @MethodSource("strategies") - public void blockingProducerTest(Class strategyClass) throws Exception + @MethodSource("pooledStrategies") + public void blockingProducerTest(Class threadPoolClass, Class strategyClass) throws Exception { - final int TASKS = 3 * _threads.getMaxThreads(); - final BlockingQueue q = new ArrayBlockingQueue<>(_threads.getMaxThreads()); + ThreadPool threadPool = threadPoolClass.getDeclaredConstructor().newInstance(); + LifeCycle.start(threadPool); + final int TASKS = 256; + final BlockingQueue q = new ArrayBlockingQueue<>(1024); Producer producer = new TestProducer() { @@ -158,12 +176,12 @@ public Runnable produce() if (id >= 0) { - while (_threads.isRunning()) + while (((LifeCycle)threadPool).isRunning()) { try { final CountDownLatch latch = q.take(); - return () -> latch.countDown(); + return latch::countDown; } catch (InterruptedException e) { @@ -176,32 +194,30 @@ public Runnable produce() } }; - ExecutionStrategy strategy = newExecutionStrategy(strategyClass, producer, _threads); + ExecutionStrategy strategy = newExecutionStrategy(strategyClass, producer, threadPool); strategy.dispatch(); final CountDownLatch latch = new CountDownLatch(TASKS); - _threads.execute(new Runnable() + threadPool.execute(() -> { - @Override - public void run() + try { - try - { - for (int t = TASKS; t-- > 0; ) - { - Thread.sleep(20); - q.offer(latch); - } - } - catch (Exception e) + for (int t = TASKS; t-- > 0; ) { - e.printStackTrace(); + Thread.sleep(5); + q.offer(latch); } } + catch (Exception e) + { + e.printStackTrace(); + } }); assertTrue(latch.await(30, TimeUnit.SECONDS), String.format("Timed out waiting for latch: %s%ntasks=%d latch=%d q=%d%n%s", - strategy, TASKS, latch.getCount(), q.size(), _threads.dump())); + strategy, TASKS, latch.getCount(), q.size(), threadPool instanceof Dumpable dumpable ? dumpable.dump() : "")); + + LifeCycle.stop(threadPool); } } From 8d66b30a37651b50ca688836209348bba937bbec Mon Sep 17 00:00:00 2001 From: gregw Date: Wed, 20 Mar 2024 14:21:47 +0100 Subject: [PATCH 02/10] Update from review --- .../eclipse/jetty/util/VirtualThreads.java | 40 ++++++++++++------- .../jetty/util/thread/QueuedThreadPool.java | 2 +- .../jetty/util/thread/VirtualThreadPool.java | 13 +++--- .../util/thread/VirtualThreadPoolTest.java | 6 +-- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java index 43486fac2118..bd5819f9432c 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java @@ -32,21 +32,9 @@ public class VirtualThreads { private static final Logger LOG = LoggerFactory.getLogger(VirtualThreads.class); - private static final Executor executor = probeVirtualThreadExecutor(); + private static final ThreadFactoryExecutor executor = getNamedVirtualThreadFactoryExecutor(null); private static final Method isVirtualThread = probeIsVirtualThread(); - private static Executor probeVirtualThreadExecutor() - { - try - { - return (Executor)Executors.class.getMethod("newVirtualThreadPerTaskExecutor").invoke(null); - } - catch (Throwable x) - { - return null; - } - } - private static Method probeIsVirtualThread() { try @@ -125,13 +113,26 @@ public static boolean isVirtualThread() * @param namePrefix the prefix to use for the name of the virtual threads * @return a virtual threads {@code Executor} that will name the virtual threads according to the provided name prefix. */ - public static ThreadFactoryExecutor getNamedVirtualThreadsExecutor(String namePrefix) + public static Executor getNamedVirtualThreadsExecutor(String namePrefix) + { + return getNamedVirtualThreadFactoryExecutor(namePrefix); + } + + /** + * Get a virtual threads {@code Executor} that names the virtual threads according to the provided name prefix. + * While named virtual threads enable observability they do also incur a minor performance penalty. + * + * @param namePrefix the prefix to use for the name of the virtual threads + * @return a virtual threads {@code Executor} that will name the virtual threads according to the provided name prefix. + */ + public static ThreadFactoryExecutor getNamedVirtualThreadFactoryExecutor(String namePrefix) { try { Class builderClass = Class.forName("java.lang.Thread$Builder"); Object threadBuilder = Thread.class.getMethod("ofVirtual").invoke(null); - threadBuilder = builderClass.getMethod("name", String.class, long.class).invoke(threadBuilder, namePrefix, 0L); + if (StringUtil.isNotBlank(namePrefix)) + threadBuilder = builderClass.getMethod("name", String.class, long.class).invoke(threadBuilder, namePrefix, 0L); ThreadFactory factory = (ThreadFactory)builderClass.getMethod("factory").invoke(threadBuilder); Executor executor = (Executor)Executors.class.getMethod("newThreadPerTaskExecutor", ThreadFactory.class).invoke(null, factory); return new ThreadFactoryExecutor() @@ -164,6 +165,15 @@ public static Executor getDefaultVirtualThreadsExecutor() return executor; } + /** + * Get a default virtual thread per task {@code Executor}. + * @return a default virtual thread per task {@code Executor} + */ + public static ThreadFactoryExecutor getDefaultVirtualThreadFactoryExecutor() + { + return executor; + } + /** * @param executor the {@code Executor} to obtain a virtual threads {@code Executor} from * @return a virtual threads {@code Executor} obtained from the given {@code Executor} diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java index db93036550c3..94e6d0a21c75 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java @@ -77,7 +77,7 @@ * */ @ManagedObject("A thread pool") -public class QueuedThreadPool extends ContainerLifeCycle implements ThreadPool, ThreadFactory, SizedThreadPool, Dumpable, TryExecutor, VirtualThreads.Configurable +public class QueuedThreadPool extends ContainerLifeCycle implements ThreadFactory, SizedThreadPool, Dumpable, TryExecutor, VirtualThreads.Configurable { private static final Logger LOG = LoggerFactory.getLogger(QueuedThreadPool.class); private static final Runnable NOOP = () -> diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java index 68e3c000e275..707fe946fc8a 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.util.thread; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; @@ -32,13 +33,13 @@ public class VirtualThreadPool extends ContainerLifeCycle implements ThreadFacto { private static final Logger LOG = LoggerFactory.getLogger(VirtualThreadPool.class); - private String _name = "vtp-%x".formatted(hashCode()); + private String _name = null; private VirtualThreads.ThreadFactoryExecutor _virtualExecutor; private final AutoLock.WithCondition _joinLock = new AutoLock.WithCondition(); public VirtualThreadPool() { - if (VirtualThreads.getDefaultVirtualThreadsExecutor() == null) + if (!VirtualThreads.areSupported()) throw new IllegalStateException("Virtual Threads not supported"); } @@ -60,7 +61,7 @@ public void setName(String name) { if (isRunning()) throw new IllegalStateException(getState()); - if (StringUtil.isBlank(name)) + if (StringUtil.isBlank(name) && name != null) throw new IllegalArgumentException("Blank name"); _name = name; } @@ -68,9 +69,9 @@ public void setName(String name) @Override protected void doStart() throws Exception { - _virtualExecutor = VirtualThreads.getNamedVirtualThreadsExecutor(_name); - if (_virtualExecutor == null) - throw new IllegalStateException("Virtual Threads not supported: " + _name); + _virtualExecutor = Objects.requireNonNull(StringUtil.isBlank(_name) + ? VirtualThreads.getDefaultVirtualThreadFactoryExecutor() + : VirtualThreads.getNamedVirtualThreadFactoryExecutor(_name)); super.doStart(); } diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java index 35879f0d9ba7..ae0c9399af21 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java @@ -34,7 +34,6 @@ public class VirtualThreadPoolTest public void testNamed() throws Exception { VirtualThreadPool vtp = new VirtualThreadPool(); - assertThat(vtp.getName(), startsWith("vtp")); vtp.setName("namedV"); vtp.start(); @@ -50,7 +49,6 @@ public void testNamed() throws Exception public void testJoin() throws Exception { VirtualThreadPool vtp = new VirtualThreadPool(); - assertThat(vtp.getName(), startsWith("vtp")); vtp.start(); CountDownLatch running = new CountDownLatch(1); @@ -80,7 +78,6 @@ public void testJoin() throws Exception public void testExecute() throws Exception { VirtualThreadPool vtp = new VirtualThreadPool(); - assertThat(vtp.getName(), startsWith("vtp")); vtp.start(); CountDownLatch ran = new CountDownLatch(1); @@ -93,7 +90,6 @@ public void testExecute() throws Exception public void testTry() throws Exception { VirtualThreadPool vtp = new VirtualThreadPool(); - assertThat(vtp.getName(), startsWith("vtp")); vtp.start(); CountDownLatch ran = new CountDownLatch(1); @@ -106,7 +102,7 @@ public void testTry() throws Exception public void testThread() throws Exception { VirtualThreadPool vtp = new VirtualThreadPool(); - assertThat(vtp.getName(), startsWith("vtp")); + vtp.setName("vtp"); vtp.start(); CountDownLatch ran = new CountDownLatch(1); From f722a1e391a233b9306a2433b50005659b4cccf1 Mon Sep 17 00:00:00 2001 From: gregw Date: Wed, 20 Mar 2024 16:05:14 +0100 Subject: [PATCH 03/10] Keep virtual main thread running to prevent JVM exit --- .../jetty/util/thread/VirtualThreadPool.java | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java index 707fe946fc8a..d7f258fec3da 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java @@ -13,7 +13,10 @@ package org.eclipse.jetty.util.thread; +import java.util.Collections; import java.util.Objects; +import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; @@ -34,7 +37,9 @@ public class VirtualThreadPool extends ContainerLifeCycle implements ThreadFacto private static final Logger LOG = LoggerFactory.getLogger(VirtualThreadPool.class); private String _name = null; + private Set _threads; private VirtualThreads.ThreadFactoryExecutor _virtualExecutor; + private Thread _main; private final AutoLock.WithCondition _joinLock = new AutoLock.WithCondition(); public VirtualThreadPool() @@ -66,12 +71,46 @@ public void setName(String name) _name = name; } + public void setTracking(boolean tracking) + { + if (isRunning()) + throw new IllegalStateException(getState()); + _threads = tracking ? Collections.newSetFromMap(new WeakHashMap<>()) : null; + } + + public boolean isTracking() + { + return _threads != null; + } + @Override protected void doStart() throws Exception { + _main = new Thread("virtual main") + { + @Override + public void run() + { + try (AutoLock.WithCondition l = _joinLock.lock()) + { + while (isRunning()) + { + l.await(); + } + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + } + }; + _main.start(); + _virtualExecutor = Objects.requireNonNull(StringUtil.isBlank(_name) ? VirtualThreads.getDefaultVirtualThreadFactoryExecutor() : VirtualThreads.getNamedVirtualThreadFactoryExecutor(_name)); + if (_threads != null) + _virtualExecutor = new TrackingVirtualExecutor(_virtualExecutor); super.doStart(); } @@ -80,6 +119,7 @@ protected void doStop() throws Exception { super.doStop(); _virtualExecutor = null; + _main = null; try (AutoLock.WithCondition l = _joinLock.lock()) { @@ -160,4 +200,49 @@ public void execute(Runnable task) { _virtualExecutor.execute(task); } + + private class TrackingVirtualExecutor implements VirtualThreads.ThreadFactoryExecutor + { + private final VirtualThreads.ThreadFactoryExecutor _threadFactoryExecutor; + + private TrackingVirtualExecutor(VirtualThreads.ThreadFactoryExecutor threadFactoryExecutor) + { + _threadFactoryExecutor = threadFactoryExecutor; + } + + @Override + public void execute(Runnable task) + { + _threadFactoryExecutor.execute(() -> + { + try + { + _threads.add(Thread.currentThread()); + task.run(); + } + finally + { + _threads.remove(Thread.currentThread()); + } + }); + } + + @Override + public Thread newThread(Runnable task) + { + Thread thread = _threadFactoryExecutor.newThread(() -> + { + try + { + task.run(); + } + finally + { + _threads.remove(Thread.currentThread()); + } + }); + _threads.add(thread); + return thread; + } + } } From 66fa0bc1794ec798be116d1e4dfba09871801286 Mon Sep 17 00:00:00 2001 From: gregw Date: Fri, 22 Mar 2024 11:08:00 +0100 Subject: [PATCH 04/10] revert module names --- .../etc/jetty-threadpool-all-virtual.xml | 15 +++++++ .../config/etc/jetty-threadpool-hybrid.xml | 43 ------------------- .../config/etc/jetty-threadpool-virtual.xml | 30 ++++++++++++- .../config/modules/threadpool-all-virtual.mod | 19 ++++++++ .../main/config/modules/threadpool-hybrid.mod | 41 ------------------ .../config/modules/threadpool-virtual.mod | 28 +++++++++++- 6 files changed, 89 insertions(+), 87 deletions(-) create mode 100644 jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml delete mode 100644 jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-hybrid.xml create mode 100644 jetty-core/jetty-server/src/main/config/modules/threadpool-all-virtual.mod delete mode 100644 jetty-core/jetty-server/src/main/config/modules/threadpool-hybrid.mod diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml new file mode 100644 index 000000000000..09e9094ce1d4 --- /dev/null +++ b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml @@ -0,0 +1,15 @@ + + + + + + + + + + org.eclipse.jetty + + All Virtual threads are enabled. + + + diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-hybrid.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-hybrid.xml deleted file mode 100644 index b51e95782fc3..000000000000 --- a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-hybrid.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - -virtual- - - - 0 - - - - - - - - - - - - - - - - - org.eclipse.jetty - - Hybrid Virtual threads are enabled. - - - diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml index 469bb527f088..71dc9444ba8d 100644 --- a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml +++ b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml @@ -2,8 +2,36 @@ - + + + + + + + + + + + + + -virtual- + + + 0 + + + + + + + + + + + + + diff --git a/jetty-core/jetty-server/src/main/config/modules/threadpool-all-virtual.mod b/jetty-core/jetty-server/src/main/config/modules/threadpool-all-virtual.mod new file mode 100644 index 000000000000..1f9526b4f30e --- /dev/null +++ b/jetty-core/jetty-server/src/main/config/modules/threadpool-all-virtual.mod @@ -0,0 +1,19 @@ +[description] +Enables and configures the Server ThreadPool with support for virtual threads to be used for all threads. +There is some risk of CPU pinning with this configuration. Only supported in Java 21 or later. + +[depends] +logging + +[provides] +threadpool + +[xml] +etc/jetty-threadpool-all-virtual.xml + +[ini-template] +# tag::documentation[] +## Platform threads name prefix. +#jetty.threadPool.namePrefix=vtp + +# end::documentation[] diff --git a/jetty-core/jetty-server/src/main/config/modules/threadpool-hybrid.mod b/jetty-core/jetty-server/src/main/config/modules/threadpool-hybrid.mod deleted file mode 100644 index ddc3f10f4bce..000000000000 --- a/jetty-core/jetty-server/src/main/config/modules/threadpool-hybrid.mod +++ /dev/null @@ -1,41 +0,0 @@ -[description] -Enables and configures the Server ThreadPool with support for mix platform and virtual threads in Java 21 or later. - -[depends] -logging - -[provides] -threadpool - -[xml] -etc/jetty-threadpool-hybrid.xml - -[ini-template] -# tag::documentation[] -## Platform threads name prefix. -#jetty.threadPool.namePrefix=qtp - -## Minimum number of pooled threads. -#jetty.threadPool.minThreads=10 - -## Maximum number of pooled threads. -#jetty.threadPool.maxThreads=200 - -## Number of reserved threads (-1 for heuristic). -#jetty.threadPool.reservedThreads=-1 - -## Thread idle timeout (in milliseconds). -#jetty.threadPool.idleTimeout=60000 - -## The max number of idle threads that can be evicted in one idleTimeout period. -#jetty.threadPool.maxEvictCount=1 - -## Whether to output a detailed dump. -#jetty.threadPool.detailedDump=false - -## Virtual threads name prefix. -#jetty.threadPool.virtual.namePrefix=qtp-virtual- - -## Whether virtual threads inherits the values of inheritable thread locals. -#jetty.threadPool.virtual.inheritInheritableThreadLocals=true -# end::documentation[] diff --git a/jetty-core/jetty-server/src/main/config/modules/threadpool-virtual.mod b/jetty-core/jetty-server/src/main/config/modules/threadpool-virtual.mod index 81644499a796..88ab2a0c2749 100644 --- a/jetty-core/jetty-server/src/main/config/modules/threadpool-virtual.mod +++ b/jetty-core/jetty-server/src/main/config/modules/threadpool-virtual.mod @@ -1,5 +1,6 @@ [description] -Enables and configures the Server ThreadPool with support for virtual threads in Java 21 or later. +Enables and configures the Server ThreadPool with support for virtual threads to be used for blocking tasks. +Only supported in Java 21 or later. [depends] logging @@ -13,6 +14,29 @@ etc/jetty-threadpool-virtual.xml [ini-template] # tag::documentation[] ## Platform threads name prefix. -#jetty.threadPool.namePrefix=vtp +#jetty.threadPool.namePrefix=qtp +## Minimum number of pooled threads. +#jetty.threadPool.minThreads=10 + +## Maximum number of pooled threads. +#jetty.threadPool.maxThreads=200 + +## Number of reserved threads (-1 for heuristic). +#jetty.threadPool.reservedThreads=-1 + +## Thread idle timeout (in milliseconds). +#jetty.threadPool.idleTimeout=60000 + +## The max number of idle threads that can be evicted in one idleTimeout period. +#jetty.threadPool.maxEvictCount=1 + +## Whether to output a detailed dump. +#jetty.threadPool.detailedDump=false + +## Virtual threads name prefix. +#jetty.threadPool.virtual.namePrefix=qtp-virtual- + +## Whether virtual threads inherits the values of inheritable thread locals. +#jetty.threadPool.virtual.inheritInheritableThreadLocals=true # end::documentation[] From 77e185ae4ba1df87ac52e934c1fdb5ac09712581 Mon Sep 17 00:00:00 2001 From: gregw Date: Wed, 1 May 2024 11:49:18 +1000 Subject: [PATCH 05/10] Updates from review --- .../eclipse/jetty/util/VirtualThreads.java | 42 +------- .../jetty/util/thread/TrackingExecutor.java | 101 ++++++++++++++++++ .../jetty/util/thread/VirtualThreadPool.java | 96 +++++------------ .../util/thread/VirtualThreadPoolTest.java | 98 +++++++++++++++-- 4 files changed, 221 insertions(+), 116 deletions(-) create mode 100644 jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java index bd5819f9432c..08ffd1f000a3 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/VirtualThreads.java @@ -32,7 +32,7 @@ public class VirtualThreads { private static final Logger LOG = LoggerFactory.getLogger(VirtualThreads.class); - private static final ThreadFactoryExecutor executor = getNamedVirtualThreadFactoryExecutor(null); + private static final Executor executor = getNamedVirtualThreadsExecutor(null); private static final Method isVirtualThread = probeIsVirtualThread(); private static Method probeIsVirtualThread() @@ -114,18 +114,6 @@ public static boolean isVirtualThread() * @return a virtual threads {@code Executor} that will name the virtual threads according to the provided name prefix. */ public static Executor getNamedVirtualThreadsExecutor(String namePrefix) - { - return getNamedVirtualThreadFactoryExecutor(namePrefix); - } - - /** - * Get a virtual threads {@code Executor} that names the virtual threads according to the provided name prefix. - * While named virtual threads enable observability they do also incur a minor performance penalty. - * - * @param namePrefix the prefix to use for the name of the virtual threads - * @return a virtual threads {@code Executor} that will name the virtual threads according to the provided name prefix. - */ - public static ThreadFactoryExecutor getNamedVirtualThreadFactoryExecutor(String namePrefix) { try { @@ -134,21 +122,7 @@ public static ThreadFactoryExecutor getNamedVirtualThreadFactoryExecutor(String if (StringUtil.isNotBlank(namePrefix)) threadBuilder = builderClass.getMethod("name", String.class, long.class).invoke(threadBuilder, namePrefix, 0L); ThreadFactory factory = (ThreadFactory)builderClass.getMethod("factory").invoke(threadBuilder); - Executor executor = (Executor)Executors.class.getMethod("newThreadPerTaskExecutor", ThreadFactory.class).invoke(null, factory); - return new ThreadFactoryExecutor() - { - @Override - public void execute(Runnable command) - { - executor.execute(command); - } - - @Override - public Thread newThread(Runnable r) - { - return factory.newThread(r); - } - }; + return (Executor)Executors.class.getMethod("newThreadPerTaskExecutor", ThreadFactory.class).invoke(null, factory); } catch (Throwable x) { @@ -165,15 +139,6 @@ public static Executor getDefaultVirtualThreadsExecutor() return executor; } - /** - * Get a default virtual thread per task {@code Executor}. - * @return a default virtual thread per task {@code Executor} - */ - public static ThreadFactoryExecutor getDefaultVirtualThreadFactoryExecutor() - { - return executor; - } - /** * @param executor the {@code Executor} to obtain a virtual threads {@code Executor} from * @return a virtual threads {@code Executor} obtained from the given {@code Executor} @@ -257,7 +222,4 @@ default void setUseVirtualThreads(boolean useVirtualThreads) private VirtualThreads() { } - - public interface ThreadFactoryExecutor extends ThreadFactory, Executor - {} } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java new file mode 100644 index 000000000000..bbed128fcb7b --- /dev/null +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java @@ -0,0 +1,101 @@ +// +// ======================================================================== +// 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.thread; + +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; + +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; +import org.eclipse.jetty.util.component.Dumpable; + +@ManagedObject("Tracking Executor wrapper") +class TrackingExecutor implements Executor, Dumpable +{ + private final Executor _threadFactoryExecutor; + private final Set _threads = ConcurrentHashMap.newKeySet(); + private boolean _detailed; + + TrackingExecutor(Executor executor, boolean detailed) + { + _threadFactoryExecutor = executor; + _detailed = detailed; + } + + @Override + public void execute(Runnable task) + { + _threadFactoryExecutor.execute(() -> + { + try + { + _threads.add(Thread.currentThread()); + task.run(); + } + finally + { + _threads.remove(Thread.currentThread()); + } + }); + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + Object[] threads = _threads.stream().map(DumpableThread::new).toArray(); + Dumpable.dumpObjects(out, indent, _threadFactoryExecutor.toString() + " size=" + threads.length, threads); + } + + public void setDetailedDump(boolean detailedDump) + { + _detailed = detailedDump; + } + + @ManagedAttribute("reports additional details in the dump") + public boolean isDetailedDump() + { + return _detailed; + } + + public int size() + { + return _threads.size(); + } + + private class DumpableThread implements Dumpable + { + private final Thread _thread; + + private DumpableThread(Thread thread) + { + _thread = thread; + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + if (_detailed) + { + Object[] stack = _thread.getStackTrace(); + Dumpable.dumpObjects(out, indent, _thread.toString(), stack); + } + else + { + Dumpable.dumpObject(out, _thread); + } + } + } +} diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java index d7f258fec3da..a19d92e80d95 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java @@ -13,17 +13,14 @@ package org.eclipse.jetty.util.thread; -import java.util.Collections; import java.util.Objects; -import java.util.Set; -import java.util.WeakHashMap; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ThreadFactory; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.VirtualThreads; import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.slf4j.Logger; @@ -32,15 +29,17 @@ /** * An implementation of {@link ThreadPool} interface that does not pool, but instead uses {@link VirtualThreads}. */ -public class VirtualThreadPool extends ContainerLifeCycle implements ThreadFactory, ThreadPool, Dumpable, TryExecutor, VirtualThreads.Configurable +@ManagedObject("A thread non-pool for virtual threads") +public class VirtualThreadPool extends ContainerLifeCycle implements ThreadPool, Dumpable, TryExecutor, VirtualThreads.Configurable { private static final Logger LOG = LoggerFactory.getLogger(VirtualThreadPool.class); + private final AutoLock.WithCondition _joinLock = new AutoLock.WithCondition(); private String _name = null; - private Set _threads; - private VirtualThreads.ThreadFactoryExecutor _virtualExecutor; + private Executor _virtualExecutor; private Thread _main; - private final AutoLock.WithCondition _joinLock = new AutoLock.WithCondition(); + private boolean _tracking; + private boolean _detailedDump; public VirtualThreadPool() { @@ -75,14 +74,27 @@ public void setTracking(boolean tracking) { if (isRunning()) throw new IllegalStateException(getState()); - _threads = tracking ? Collections.newSetFromMap(new WeakHashMap<>()) : null; + _tracking = tracking; } public boolean isTracking() { - return _threads != null; + return _tracking; + } + + @ManagedAttribute("reports additional details in the dump") + public boolean isDetailedDump() + { + return _detailedDump; } + public void setDetailedDump(boolean detailedDump) + { + _detailedDump = detailedDump; + if (_virtualExecutor instanceof TrackingExecutor trackingExecutor) + trackingExecutor.setDetailedDump(detailedDump); + } + @Override protected void doStart() throws Exception { @@ -107,10 +119,11 @@ public void run() _main.start(); _virtualExecutor = Objects.requireNonNull(StringUtil.isBlank(_name) - ? VirtualThreads.getDefaultVirtualThreadFactoryExecutor() - : VirtualThreads.getNamedVirtualThreadFactoryExecutor(_name)); - if (_threads != null) - _virtualExecutor = new TrackingVirtualExecutor(_virtualExecutor); + ? VirtualThreads.getDefaultVirtualThreadsExecutor() + : VirtualThreads.getNamedVirtualThreadsExecutor(_name)); + if (_tracking) + _virtualExecutor = new TrackingExecutor(_virtualExecutor, _detailedDump); + addBean(_virtualExecutor); super.doStart(); } @@ -127,12 +140,6 @@ protected void doStop() throws Exception } } - @Override - public Thread newThread(Runnable r) - { - return _virtualExecutor.newThread(r); - } - @Override public Executor getVirtualThreadsExecutor() { @@ -165,13 +172,13 @@ public void join() throws InterruptedException @Override public int getThreads() { - return -1; + return _virtualExecutor instanceof TrackingExecutor tracking ? tracking.size() : -1; } @Override public int getIdleThreads() { - return -1; + return 0; } @Override @@ -200,49 +207,4 @@ public void execute(Runnable task) { _virtualExecutor.execute(task); } - - private class TrackingVirtualExecutor implements VirtualThreads.ThreadFactoryExecutor - { - private final VirtualThreads.ThreadFactoryExecutor _threadFactoryExecutor; - - private TrackingVirtualExecutor(VirtualThreads.ThreadFactoryExecutor threadFactoryExecutor) - { - _threadFactoryExecutor = threadFactoryExecutor; - } - - @Override - public void execute(Runnable task) - { - _threadFactoryExecutor.execute(() -> - { - try - { - _threads.add(Thread.currentThread()); - task.run(); - } - finally - { - _threads.remove(Thread.currentThread()); - } - }); - } - - @Override - public Thread newThread(Runnable task) - { - Thread thread = _threadFactoryExecutor.newThread(() -> - { - try - { - task.run(); - } - finally - { - _threads.remove(Thread.currentThread()); - } - }); - _threads.add(thread); - return thread; - } - } } diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java index ae0c9399af21..7371d66276de 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java @@ -17,14 +17,15 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.util.StringUtil; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledForJreRange; import org.junit.jupiter.api.condition.JRE; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @DisabledForJreRange(max = JRE.JAVA_20) @@ -99,18 +100,97 @@ public void testTry() throws Exception } @Test - public void testThread() throws Exception + public void testTrackingDump() throws Exception { VirtualThreadPool vtp = new VirtualThreadPool(); - vtp.setName("vtp"); + vtp.setTracking(true); vtp.start(); - CountDownLatch ran = new CountDownLatch(1); - Thread t = vtp.newThread(ran::countDown); - assertThat(t.getName(), startsWith(vtp.getName())); - assertFalse(ran.await(1, TimeUnit.SECONDS)); - t.start(); - assertTrue(ran.await(5, TimeUnit.SECONDS)); + assertThat(vtp.getVirtualThreadsExecutor(), instanceOf(TrackingExecutor.class)); + TrackingExecutor trackingExecutor = (TrackingExecutor)vtp.getVirtualThreadsExecutor(); + assertThat(trackingExecutor.size(), is(0)); + + CountDownLatch running = new CountDownLatch(4); + Waiter waiter = new Waiter(running, false); + Waiter spinner = new Waiter(running, true); + vtp.execute(waiter); + vtp.execute(spinner); + vtp.execute(waiter); + vtp.execute(spinner); + + assertTrue(running.await(5, TimeUnit.SECONDS)); + assertThat(trackingExecutor.size(), is(4)); + + vtp.setDetailedDump(false); + String dump = vtp.dump(); + assertThat(count(dump, "VirtualThread[#"), is(4)); + assertThat(count(dump, "/runnable@"), is(2)); + assertThat(count(dump, "/timed_waiting"), is(2)); + assertThat(count(dump, "VirtualThreadPoolTest.java"), is(0)); + + vtp.setDetailedDump(true); + dump = vtp.dump(); + assertThat(count(dump, "VirtualThread[#"), is(4)); + assertThat(count(dump, "/runnable@"), is(2)); + assertThat(count(dump, "/timed_waiting"), is(2)); + assertThat(count(dump, "VirtualThreadPoolTest.java"), is(4)); + assertThat(count(dump, "CountDownLatch.await("), is(2)); + + waiter.countDown(); vtp.stop(); } + + public static int count(String str, String subStr) + { + if (StringUtil.isEmpty(str)) + return 0; + + int count = 0; + int idx = 0; + + while ((idx = str.indexOf(subStr, idx)) != -1) + { + count++; + idx += subStr.length(); + } + + return count; + } + + private static class Waiter extends CountDownLatch implements Runnable + { + private final CountDownLatch _running; + private final boolean _spin; + + public Waiter(CountDownLatch running, boolean spin) + { + super(1); + _running = running; + _spin = spin; + } + + @Override + public void run() + { + try + { + _running.countDown(); + + while (_spin && getCount() > 0) + Thread.onSpinWait(); + + if (!await(10, TimeUnit.SECONDS)) + throw new IllegalStateException(); + + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + finally + { + System.err.println("RAN!" + Thread.currentThread()); + } + } + } } From 96cb6e8f53084ce1cd007e297efed5fac4b4f5d8 Mon Sep 17 00:00:00 2001 From: gregw Date: Thu, 2 May 2024 09:29:06 +1000 Subject: [PATCH 06/10] Improved test of dump to always stop spinners --- .../util/thread/VirtualThreadPoolTest.java | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java index 7371d66276de..e2fc3e9352c8 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java @@ -113,31 +113,37 @@ public void testTrackingDump() throws Exception CountDownLatch running = new CountDownLatch(4); Waiter waiter = new Waiter(running, false); Waiter spinner = new Waiter(running, true); - vtp.execute(waiter); - vtp.execute(spinner); - vtp.execute(waiter); - vtp.execute(spinner); - - assertTrue(running.await(5, TimeUnit.SECONDS)); - assertThat(trackingExecutor.size(), is(4)); - - vtp.setDetailedDump(false); - String dump = vtp.dump(); - assertThat(count(dump, "VirtualThread[#"), is(4)); - assertThat(count(dump, "/runnable@"), is(2)); - assertThat(count(dump, "/timed_waiting"), is(2)); - assertThat(count(dump, "VirtualThreadPoolTest.java"), is(0)); - - vtp.setDetailedDump(true); - dump = vtp.dump(); - assertThat(count(dump, "VirtualThread[#"), is(4)); - assertThat(count(dump, "/runnable@"), is(2)); - assertThat(count(dump, "/timed_waiting"), is(2)); - assertThat(count(dump, "VirtualThreadPoolTest.java"), is(4)); - assertThat(count(dump, "CountDownLatch.await("), is(2)); - - waiter.countDown(); - vtp.stop(); + try + { + vtp.execute(waiter); + vtp.execute(spinner); + vtp.execute(waiter); + vtp.execute(spinner); + + assertTrue(running.await(5, TimeUnit.SECONDS)); + assertThat(trackingExecutor.size(), is(4)); + + vtp.setDetailedDump(false); + String dump = vtp.dump(); + assertThat(count(dump, "VirtualThread[#"), is(4)); + assertThat(count(dump, "/runnable@"), is(2)); + assertThat(count(dump, "waiting"), is(2)); + assertThat(count(dump, "VirtualThreadPoolTest.java"), is(0)); + + vtp.setDetailedDump(true); + dump = vtp.dump(); + assertThat(count(dump, "VirtualThread[#"), is(4)); + assertThat(count(dump, "/runnable@"), is(2)); + assertThat(count(dump, "waiting"), is(2)); + assertThat(count(dump, "VirtualThreadPoolTest.java"), is(4)); + assertThat(count(dump, "CountDownLatch.await("), is(2)); + } + finally + { + waiter.countDown(); + spinner.countDown(); + vtp.stop(); + } } public static int count(String str, String subStr) From 061be040d2c04c256a51041f54d13b3bb5b6088c Mon Sep 17 00:00:00 2001 From: gregw Date: Sat, 4 May 2024 08:06:04 +1000 Subject: [PATCH 07/10] Updates from review --- .../etc/jetty-threadpool-all-virtual.xml | 2 +- .../jetty/util/thread/TrackingExecutor.java | 7 +++--- .../jetty/util/thread/VirtualThreadPool.java | 24 +++++++++++++------ .../util/thread/VirtualThreadPoolTest.java | 7 ------ 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml index 09e9094ce1d4..67943a3cf0bf 100644 --- a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml +++ b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml @@ -9,7 +9,7 @@ org.eclipse.jetty - All Virtual threads are enabled. + Virtual threads enabled. Using all virtual threads. diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java index bbed128fcb7b..8a8bd258e4af 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java @@ -23,7 +23,7 @@ import org.eclipse.jetty.util.component.Dumpable; @ManagedObject("Tracking Executor wrapper") -class TrackingExecutor implements Executor, Dumpable +public class TrackingExecutor implements Executor, Dumpable { private final Executor _threadFactoryExecutor; private final Set _threads = ConcurrentHashMap.newKeySet(); @@ -40,14 +40,15 @@ public void execute(Runnable task) { _threadFactoryExecutor.execute(() -> { + Thread thread = Thread.currentThread(); try { - _threads.add(Thread.currentThread()); + _threads.add(thread); task.run(); } finally { - _threads.remove(Thread.currentThread()); + _threads.remove(thread); } }); } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java index a19d92e80d95..4873403b1e96 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java @@ -38,6 +38,7 @@ public class VirtualThreadPool extends ContainerLifeCycle implements ThreadPool, private String _name = null; private Executor _virtualExecutor; private Thread _main; + private boolean _externalExecutor; private boolean _tracking; private boolean _detailedDump; @@ -98,7 +99,7 @@ public void setDetailedDump(boolean detailedDump) @Override protected void doStart() throws Exception { - _main = new Thread("virtual main") + _main = new Thread("keepalive") { @Override public void run() @@ -118,10 +119,14 @@ public void run() }; _main.start(); - _virtualExecutor = Objects.requireNonNull(StringUtil.isBlank(_name) - ? VirtualThreads.getDefaultVirtualThreadsExecutor() - : VirtualThreads.getNamedVirtualThreadsExecutor(_name)); - if (_tracking) + if (_virtualExecutor == null) + { + _externalExecutor = false; + _virtualExecutor = Objects.requireNonNull(StringUtil.isBlank(_name) + ? VirtualThreads.getDefaultVirtualThreadsExecutor() + : VirtualThreads.getNamedVirtualThreadsExecutor(_name)); + } + if (_tracking && !(_virtualExecutor instanceof TrackingExecutor)) _virtualExecutor = new TrackingExecutor(_virtualExecutor, _detailedDump); addBean(_virtualExecutor); super.doStart(); @@ -131,7 +136,9 @@ public void run() protected void doStop() throws Exception { super.doStop(); - _virtualExecutor = null; + removeBean(_virtualExecutor); + if (!_externalExecutor) + _virtualExecutor = null; _main = null; try (AutoLock.WithCondition l = _joinLock.lock()) @@ -149,7 +156,10 @@ public Executor getVirtualThreadsExecutor() @Override public void setVirtualThreadsExecutor(Executor executor) { - throw new UnsupportedOperationException("cannot set VirtualThreadExecutor"); + if (isRunning()) + throw new IllegalStateException(getState()); + _externalExecutor = executor != null; + _virtualExecutor = executor; } @Override diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java index e2fc3e9352c8..1f9ef396a77f 100644 --- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java +++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/thread/VirtualThreadPoolTest.java @@ -181,22 +181,15 @@ public void run() try { _running.countDown(); - while (_spin && getCount() > 0) Thread.onSpinWait(); - if (!await(10, TimeUnit.SECONDS)) throw new IllegalStateException(); - } catch (InterruptedException e) { throw new RuntimeException(e); } - finally - { - System.err.println("RAN!" + Thread.currentThread()); - } } } } From 34e1ea26b89e600e993e8047396b18d9e6d5d41d Mon Sep 17 00:00:00 2001 From: gregw Date: Mon, 6 May 2024 20:57:04 +1000 Subject: [PATCH 08/10] Updates from review --- .../etc/jetty-threadpool-all-virtual.xml | 2 +- .../config/etc/jetty-threadpool-virtual.xml | 2 +- .../jetty/util/thread/TrackingExecutor.java | 2 +- .../jetty/util/thread/VirtualThreadPool.java | 18 ++++++++++++------ 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml index 67943a3cf0bf..338d08a745cf 100644 --- a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml +++ b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml @@ -9,7 +9,7 @@ org.eclipse.jetty - Virtual threads enabled. Using all virtual threads. + Virtual threads enabled. Using virtual threads for all Jetty tasks. diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml index 71dc9444ba8d..099d33ddc4ba 100644 --- a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml +++ b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml @@ -37,7 +37,7 @@ org.eclipse.jetty - Virtual threads are enabled. + Virtual threads enabled. Using virtual threads only for application tasks. diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java index 8a8bd258e4af..9714d5490741 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/TrackingExecutor.java @@ -29,7 +29,7 @@ public class TrackingExecutor implements Executor, Dumpable private final Set _threads = ConcurrentHashMap.newKeySet(); private boolean _detailed; - TrackingExecutor(Executor executor, boolean detailed) + public TrackingExecutor(Executor executor, boolean detailed) { _threadFactoryExecutor = executor; _detailed = detailed; diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java index 4873403b1e96..6182decf4f1a 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java @@ -71,6 +71,17 @@ public void setName(String name) _name = name; } + /** + * + * @return {@code true} if the virtual threads will be tracked. + * @see TrackingExecutor + */ + @ManagedAttribute("virtual threads are tracked") + public boolean isTracking() + { + return _tracking; + } + public void setTracking(boolean tracking) { if (isRunning()) @@ -78,11 +89,6 @@ public void setTracking(boolean tracking) _tracking = tracking; } - public boolean isTracking() - { - return _tracking; - } - @ManagedAttribute("reports additional details in the dump") public boolean isDetailedDump() { @@ -99,7 +105,7 @@ public void setDetailedDump(boolean detailedDump) @Override protected void doStart() throws Exception { - _main = new Thread("keepalive") + _main = new Thread("jetty-virtual-thread-pool-keepalive") { @Override public void run() From 1a225ec9605c3c31ac5f61e98ddf6f28cfeda835 Mon Sep 17 00:00:00 2001 From: gregw Date: Mon, 6 May 2024 21:21:25 +1000 Subject: [PATCH 09/10] updates from review --- .../src/main/config/etc/jetty-threadpool-virtual.xml | 2 +- .../java/org/eclipse/jetty/util/thread/VirtualThreadPool.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml index 099d33ddc4ba..387b94b86cd9 100644 --- a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml +++ b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml @@ -37,7 +37,7 @@ org.eclipse.jetty - Virtual threads enabled. Using virtual threads only for application tasks. + Virtual threads enabled. Using virtual threads for Jetty application tasks. diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java index 6182decf4f1a..bd90805b98da 100644 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java +++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/VirtualThreadPool.java @@ -72,7 +72,7 @@ public void setName(String name) } /** - * + * Get if this pool is tracking virtual threads. * @return {@code true} if the virtual threads will be tracked. * @see TrackingExecutor */ From 0a4e6a544d35e7dff4deb998924586b2c1b1f23e Mon Sep 17 00:00:00 2001 From: gregw Date: Tue, 7 May 2024 16:38:13 +1000 Subject: [PATCH 10/10] updates from review --- .../src/main/config/etc/jetty-threadpool-all-virtual.xml | 2 +- .../src/main/config/etc/jetty-threadpool-virtual.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml index 338d08a745cf..2719a4011a3d 100644 --- a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml +++ b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-all-virtual.xml @@ -9,7 +9,7 @@ org.eclipse.jetty - Virtual threads enabled. Using virtual threads for all Jetty tasks. + Virtual threads enabled. Using virtual threads for all tasks. diff --git a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml index 387b94b86cd9..77109d6f83b7 100644 --- a/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml +++ b/jetty-core/jetty-server/src/main/config/etc/jetty-threadpool-virtual.xml @@ -37,7 +37,7 @@ org.eclipse.jetty - Virtual threads enabled. Using virtual threads for Jetty application tasks. + Virtual threads enabled. Using virtual threads for application tasks.