From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Martijn Muijsers Date: Wed, 9 Aug 2023 15:04:56 +0200 Subject: [PATCH] Virtual thread support License: GPL-3.0 (https://www.gnu.org/licenses/gpl-3.0.html) Gale - https://galemc.org diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java index 850ccd53c5007e8b18344ba76c9e7f0003decaff..88102f6ba8352a080125512d0bbfacdf779f4f38 100644 --- a/src/main/java/com/destroystokyo/paper/Metrics.java +++ b/src/main/java/com/destroystokyo/paper/Metrics.java @@ -973,6 +973,22 @@ public class Metrics { })); // Gale end - SIMD support - include in metrics + // Gale start - virtual thread support - include in metrics + Map> virtualThreadSupportMap = new HashMap<>(2); + { + Map entry = new HashMap<>(2); + boolean isSupported = org.galemc.gale.virtualthread.VirtualThreadService.isSupported(); + try { + int javaMajorVersion = org.galemc.gale.virtualthread.VirtualThreadService.getJavaMajorVersion(); + entry.put(isSupported + " (Java " + javaMajorVersion + ")", 1); + } catch (Exception ignored) { + entry.put(String.valueOf(isSupported), 1); + } + virtualThreadSupportMap.put(String.valueOf(isSupported), entry); + } + metrics.addCustomChart(new Metrics.DrilldownPie("virtual_thread_support", () -> virtualThreadSupportMap)); + // Gale end - virtual thread support - include in metrics + } } diff --git a/src/main/java/org/galemc/gale/virtualthread/DirectVirtualThreadService.java b/src/main/java/org/galemc/gale/virtualthread/DirectVirtualThreadService.java new file mode 100644 index 0000000000000000000000000000000000000000..02bd280f549845c6fbf91ad26825d62eccf84d11 --- /dev/null +++ b/src/main/java/org/galemc/gale/virtualthread/DirectVirtualThreadService.java @@ -0,0 +1,50 @@ +// Gale - virtual thread support + +package org.galemc.gale.virtualthread; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ThreadFactory; + +/** + * An implementation of {@link VirtualThreadService} that can create virtual threads directly. + * + * @author Martijn Muijsers + */ +final class DirectVirtualThreadService extends VirtualThreadService { + + private DirectVirtualThreadService() { + super(); + } + + @Override + public @NotNull ThreadFactory createFactory() { + // Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable + throw new UnsupportedOperationException(); +// return Thread.ofVirtual().factory(); + } + + @Override + public @NotNull Thread start(@NotNull Runnable task) { + // Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable + throw new UnsupportedOperationException(); +// Objects.requireNonNull(task, "The task to start a virtual thread cannot be null"); +// return Thread.ofVirtual().start(task); + } + + /** + * @return A functional {@link DirectVirtualThreadService}. + * @throws Throwable If creating virtual threads directly is not supported by the current runtime. + * This could be any {@link Throwable}, including an {@link Exception} or an {@link Error}. + */ + static @NotNull DirectVirtualThreadService create() throws Throwable { + // Disabled until Minecraft requires servers to have a Java version that can read class files compiled with functionality from Java 19+ on preview / Java 21+ on stable + throw new UnsupportedOperationException(); +// var service = new DirectVirtualThreadService(); +// // Run some tests to verify +// service.runTest(); +// // If we end up here, it works +// return service; + } + +} diff --git a/src/main/java/org/galemc/gale/virtualthread/ReflectionVirtualThreadService.java b/src/main/java/org/galemc/gale/virtualthread/ReflectionVirtualThreadService.java new file mode 100644 index 0000000000000000000000000000000000000000..9bdd3a45141938a8f09567bd8a7908c7a317791f --- /dev/null +++ b/src/main/java/org/galemc/gale/virtualthread/ReflectionVirtualThreadService.java @@ -0,0 +1,76 @@ +// Gale - virtual thread support + +package org.galemc.gale.virtualthread; + +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.concurrent.ThreadFactory; + +/** + * An implementation of {@link VirtualThreadService} that can create virtual threads using Java reflection. + * + * @author Martijn Muijsers + */ +final class ReflectionVirtualThreadService extends VirtualThreadService { + + /** + * The {@link Thread}#ofVirtual() method. + */ + private final @NotNull Method Thread_ofVirtual_method; + + /** + * The {@link Thread}.Builder#factory() method. + */ + private final @NotNull Method Thread_Builder_factory_method; + + /** + * The {@link Thread}.Builder#start(Runnable) method. + */ + private final @NotNull Method Thread_Builder_start_method; + + private ReflectionVirtualThreadService() throws Throwable { + this.Thread_ofVirtual_method = Objects.requireNonNull(Thread.class.getMethod("ofVirtual")); + // The Thread.Builder class + var Thread_Builder_class = Objects.requireNonNull(Class.forName("java.lang.Thread$Builder")); + this.Thread_Builder_factory_method = Objects.requireNonNull(Thread_Builder_class.getMethod("factory")); + this.Thread_Builder_start_method = Objects.requireNonNull(Thread_Builder_class.getMethod("start", Runnable.class)); + } + + @Override + public @NotNull ThreadFactory createFactory() { + try { + return (ThreadFactory) this.Thread_Builder_factory_method.invoke(this.Thread_ofVirtual_method.invoke(null)); + } catch (Exception e) { + // This should not be possible because it was tested in create() + throw new RuntimeException(e); + } + } + + @Override + public @NotNull Thread start(@NotNull Runnable task) { + Objects.requireNonNull(task, "The task to start a virtual thread cannot be null"); + try { + return (Thread) this.Thread_Builder_start_method.invoke(this.Thread_ofVirtual_method.invoke(null), task); + } catch (Exception e) { + // This should not be possible because it was tested in create() + throw new RuntimeException(e); + } + } + + /** + * @return A functional {@link ReflectionVirtualThreadService}. + * @throws Throwable If creating virtual threads via reflection is not supported by the current runtime. + * This could be any {@link Throwable}, including an {@link Exception} or an {@link Error}. + */ + static @NotNull ReflectionVirtualThreadService create() throws Throwable { + // This will already throw something if the reflection fails + var service = new ReflectionVirtualThreadService(); + // Run some tests to verify + service.runTest(); + // If we end up here, it works + return service; + } + +} diff --git a/src/main/java/org/galemc/gale/virtualthread/VirtualThreadService.java b/src/main/java/org/galemc/gale/virtualthread/VirtualThreadService.java new file mode 100644 index 0000000000000000000000000000000000000000..e1b2dccd750a89d89484199eeaf6c376e4f900be --- /dev/null +++ b/src/main/java/org/galemc/gale/virtualthread/VirtualThreadService.java @@ -0,0 +1,101 @@ +// Gale - virtual thread support + +package org.galemc.gale.virtualthread; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.ThreadFactory; + +/** + * An abstract service to create virtual threads. + * + * @author Martijn Muijsers + */ +public sealed abstract class VirtualThreadService permits ReflectionVirtualThreadService, DirectVirtualThreadService { + + /** + * @return A {@link ThreadFactory} that produces virtual threads. + */ + public abstract @NotNull ThreadFactory createFactory(); + + /** + * @param task The runnable for the thread to execute. + * @return A virtual thread that has been started with the given task. + */ + public abstract @NotNull Thread start(Runnable task); + + /** + * Runs a test on the {@link #createFactory} and {@link #start} methods, + * which certainly throws some {@link Throwable} if something goes wrong. + */ + protected void runTest() throws Throwable { + // This will definitely throw something if it doesn't work + try { + this.start(() -> {}).join(); + } catch (InterruptedException ignored) {} // Except InterruptedException, we don't care about that one + try { + var thread = this.createFactory().newThread(() -> {}); + thread.start(); + thread.join(); + } catch (InterruptedException ignored) {} // Except InterruptedException, we don't care about that one + // If we end up here, it works + } + + private static boolean initialized = false; + + /** + * The {@link VirtualThreadService} for the current runtime, + * or null if virtual threads are not supported, or if not {@link #initialized} yet. + */ + private static @Nullable VirtualThreadService implementation; + + /** + * @return Whether virtual threads are supported on the current runtime. + */ + public static boolean isSupported() { + return get() != null; + } + + /** + * @return The {@link VirtualThreadService} for the current runtime, + * or null if virtual threads are not {@linkplain #isSupported() supported}. + * + * This method is thread-safe only after the first time it has been fully run. + */ + public static @Nullable VirtualThreadService get() { + if (!initialized) { + initialized = true; + try { + implementation = DirectVirtualThreadService.create(); + } catch (Throwable ignored) { + try { + implementation = ReflectionVirtualThreadService.create(); + } catch (Throwable ignored2) {} + } + } + return implementation; + } + + /** + * The minimum major version of Java that is known to support using virtual threads + * (although possibly behind a feature preview flag). + */ + public static final int minimumJavaMajorVersionWithFeaturePreview = 19; + + /** + * The minimum major version of Java that is known to support using virtual threads + * even without any feature preview flags. + */ + public static final int minimumJavaMajorVersionWithoutFeaturePreview = 21; + + public static int getJavaMajorVersion() { + var version = System.getProperty("java.version"); + if (version.startsWith("1.")) { + return version.charAt(2) - '0'; + } + int dotIndex = version.indexOf("."); + return Integer.parseInt(dotIndex == -1 ? version : version.substring(0, dotIndex)); + } + +}