diff --git a/org.jgrapes.core/src/org/jgrapes/core/Components.java b/org.jgrapes.core/src/org/jgrapes/core/Components.java index 2bbb0cdb1f..cc7d047743 100644 --- a/org.jgrapes.core/src/org/jgrapes/core/Components.java +++ b/org.jgrapes.core/src/org/jgrapes/core/Components.java @@ -34,6 +34,7 @@ import java.util.WeakHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import org.jgrapes.core.annotation.ComponentManager; @@ -53,11 +54,37 @@ public class Components { private static ExecutorService defaultExecutorService - = Executors.newVirtualThreadPerTaskExecutor(); + = useVirtualThreads() ? Executors.newVirtualThreadPerTaskExecutor() + : Executors.newCachedThreadPool( + new ThreadFactory() { + @SuppressWarnings({ "PMD.CommentRequired", + "PMD.MissingOverride" }) + public Thread newThread(Runnable runnable) { + Thread thread + = Executors.defaultThreadFactory() + .newThread(runnable); + thread.setDaemon(true); + return thread; + } + }); private static ExecutorService timerExecutorService = defaultExecutorService; + /** + * JGrapes uses virtual thread by default. However, as of + * 2024, some debuggers still have problems with virtual + * threads. Therefore it is possible to switch back to + * platform threads by starting the JVM with property + * `-Djgrapes.useVirtualThreads=false`. + * + * @return true, if successful + */ + public static boolean useVirtualThreads() { + return Boolean.parseBoolean( + System.getProperty("jgrapes.useVirtualThreads", "true")); + } + private Components() { } @@ -452,7 +479,8 @@ private static class Scheduler extends Thread { */ @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") public Scheduler() { - ofVirtual().name("Components.Scheduler").start(this); + (useVirtualThreads() ? ofVirtual() : ofPlatform()) + .name("Components.Scheduler").start(this); } /** diff --git a/org.jgrapes.io/src/org/jgrapes/io/InputStreamMonitor.java b/org.jgrapes.io/src/org/jgrapes/io/InputStreamMonitor.java index 83446e60b4..5166a1cfe2 100644 --- a/org.jgrapes.io/src/org/jgrapes/io/InputStreamMonitor.java +++ b/org.jgrapes.io/src/org/jgrapes/io/InputStreamMonitor.java @@ -144,8 +144,9 @@ public void onStart(Start event) { () -> { return ByteBuffer.allocateDirect(bufferSize); }, 2); - runner = Thread.ofVirtual().name(Components.simpleObjectName(this)) - .start(this); + runner = (Components.useVirtualThreads() ? Thread.ofVirtual() + : Thread.ofPlatform()).name(Components.simpleObjectName(this)) + .start(this); } } diff --git a/org.jgrapes.io/src/org/jgrapes/io/NioDispatcher.java b/org.jgrapes.io/src/org/jgrapes/io/NioDispatcher.java index 374f4a4fad..adb649da15 100644 --- a/org.jgrapes.io/src/org/jgrapes/io/NioDispatcher.java +++ b/org.jgrapes.io/src/org/jgrapes/io/NioDispatcher.java @@ -63,8 +63,9 @@ public void onStart(Start event) { if (runner != null && !runner.isInterrupted()) { return; } - runner = Thread.ofVirtual().name(Components.simpleObjectName(this)) - .start(this); + runner = (Components.useVirtualThreads() ? Thread.ofVirtual() + : Thread.ofPlatform()).name(Components.simpleObjectName(this)) + .start(this); } } diff --git a/org.jgrapes.io/src/org/jgrapes/io/util/ManagedBufferStreamer.java b/org.jgrapes.io/src/org/jgrapes/io/util/ManagedBufferStreamer.java index 9187e05859..def701b8d9 100644 --- a/org.jgrapes.io/src/org/jgrapes/io/util/ManagedBufferStreamer.java +++ b/org.jgrapes.io/src/org/jgrapes/io/util/ManagedBufferStreamer.java @@ -23,6 +23,8 @@ import java.nio.Buffer; import java.nio.charset.Charset; import java.util.function.Consumer; + +import org.jgrapes.core.Components; import org.jgrapes.io.events.Input; /** @@ -42,9 +44,10 @@ public class ManagedBufferStreamer implements InputConsumer { * @param processor the processor */ public ManagedBufferStreamer(Consumer processor) { - Thread thread = Thread.ofVirtual().start(() -> { - processor.accept(reader); - }); + Thread thread = (Components.useVirtualThreads() ? Thread.ofVirtual() + : Thread.ofPlatform()).start(() -> { + processor.accept(reader); + }); ThreadCleaner.watch(this, thread); } diff --git a/org.jgrapes.io/src/org/jgrapes/io/util/ThreadCleaner.java b/org.jgrapes.io/src/org/jgrapes/io/util/ThreadCleaner.java index 11f89da1f4..ab11ff00ac 100644 --- a/org.jgrapes.io/src/org/jgrapes/io/util/ThreadCleaner.java +++ b/org.jgrapes.io/src/org/jgrapes/io/util/ThreadCleaner.java @@ -23,6 +23,8 @@ import java.util.HashSet; import java.util.Set; +import org.jgrapes.core.Components; + /** * Cleans up threads if some object has been garbage collected. * @@ -71,18 +73,19 @@ public RefWithThread(Object referent, Thread thread) { } static { - Thread.ofVirtual().name("ThreadCleaner").start(() -> { - while (true) { - try { - ThreadCleaner.RefWithThread ref - = (ThreadCleaner.RefWithThread) abandoned.remove(); - ref.watched.interrupt(); - watched.remove(ref); - } catch (InterruptedException e) { // NOPMD - // Nothing to do + (Components.useVirtualThreads() ? Thread.ofVirtual() + : Thread.ofPlatform()).name("ThreadCleaner").start(() -> { + while (true) { + try { + ThreadCleaner.RefWithThread ref + = (ThreadCleaner.RefWithThread) abandoned.remove(); + ref.watched.interrupt(); + watched.remove(ref); + } catch (InterruptedException e) { // NOPMD + // Nothing to do + } } - } - }); + }); } /** diff --git a/org.jgrapes.io/src/org/jgrapes/net/SocketServer.java b/org.jgrapes.io/src/org/jgrapes/net/SocketServer.java index dd92440e9c..ff471009e9 100644 --- a/org.jgrapes.io/src/org/jgrapes/net/SocketServer.java +++ b/org.jgrapes.io/src/org/jgrapes/net/SocketServer.java @@ -417,7 +417,8 @@ public void onRegistered(NioRegistration.Completed event) return; } registration = event.event().get(); - purger = Thread.ofVirtual().start(new Purger()); + purger = (Components.useVirtualThreads() ? Thread.ofVirtual() + : Thread.ofPlatform()).start(new Purger()); fire(new Ready(serverSocketChannel.getLocalAddress())); return; } diff --git a/org.jgrapes.util/src/org/jgrapes/util/FileSystemWatcher.java b/org.jgrapes.util/src/org/jgrapes/util/FileSystemWatcher.java index 4fbb755972..5f98574bb4 100644 --- a/org.jgrapes.util/src/org/jgrapes/util/FileSystemWatcher.java +++ b/org.jgrapes.util/src/org/jgrapes/util/FileSystemWatcher.java @@ -41,6 +41,7 @@ import java.util.stream.StreamSupport; import org.jgrapes.core.Channel; import org.jgrapes.core.Component; +import org.jgrapes.core.Components; import org.jgrapes.core.Event; import org.jgrapes.core.Manager; import org.jgrapes.core.annotation.Handler; @@ -195,25 +196,26 @@ private Watcher(FileSystem fileSystem) throws IOException { .stream(fileSystem.getRootDirectories().spliterator(), false) .map(Path::toString) .collect(Collectors.joining(File.pathSeparator)); - Thread.ofVirtual().name(roots + " watcher") - .start(() -> { - while (true) { - try { - WatchKey key = watchService.take(); - // Events have to be consumed - key.pollEvents(); - if (!(key.watchable() instanceof Path)) { + (Components.useVirtualThreads() ? Thread.ofVirtual() + : Thread.ofPlatform()).name(roots + " watcher") + .start(() -> { + while (true) { + try { + WatchKey key = watchService.take(); + // Events have to be consumed + key.pollEvents(); + if (!(key.watchable() instanceof Path)) { + key.reset(); + continue; + } + handleWatchEvent((Path) key.watchable()); key.reset(); - continue; + } catch (InterruptedException e) { + logger.log(Level.WARNING, e, + () -> "No WatchKey: " + e.getMessage()); } - handleWatchEvent((Path) key.watchable()); - key.reset(); - } catch (InterruptedException e) { - logger.log(Level.WARNING, e, - () -> "No WatchKey: " + e.getMessage()); } - } - }); + }); } } diff --git a/org.jgrapes.util/src/org/jgrapes/util/Password.java b/org.jgrapes.util/src/org/jgrapes/util/Password.java index e9abfcc096..67c8dc1c10 100644 --- a/org.jgrapes.util/src/org/jgrapes/util/Password.java +++ b/org.jgrapes.util/src/org/jgrapes/util/Password.java @@ -24,6 +24,8 @@ import java.util.Arrays; import java.util.Optional; +import org.jgrapes.core.Components; + /** * Stores a password in such a way that it can be cleared. Automatically * clears the storage if an object of this type becomes weakly reachable. @@ -50,19 +52,20 @@ public class Password { public Password(char[] password) { synchronized (Password.class) { if (purger == null) { - purger = Thread.ofVirtual().name("PasswordPurger").start(() -> { - while (true) { - try { - Reference passwordRef - = toBeCleared.remove(); - Optional.ofNullable(passwordRef.get()) - .ifPresent(Password::clear); - passwordRef.clear(); - } catch (InterruptedException e) { - break; + purger = (Components.useVirtualThreads() ? Thread.ofVirtual() + : Thread.ofPlatform()).name("PasswordPurger").start(() -> { + while (true) { + try { + Reference passwordRef + = toBeCleared.remove(); + Optional.ofNullable(passwordRef.get()) + .ifPresent(Password::clear); + passwordRef.clear(); + } catch (InterruptedException e) { + break; + } } - } - }); + }); } } this.password = password;