diff --git a/divinemc-server/minecraft-patches/features/0090-Async-Join-Thread.patch b/divinemc-server/minecraft-patches/features/0090-Async-Join-Thread.patch new file mode 100644 index 0000000..67fda9a --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0090-Async-Join-Thread.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Wed, 11 Jun 2025 20:15:37 +0300 +Subject: [PATCH] Async Join Thread + + +diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +index 9285697a1da3f216e03b8ea824f07f7f7c716c53..de4d2bd780c98c8c06db5e9375d447dae4d4429e 100644 +--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -186,22 +186,25 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + return; + } + // Paper end - Add Velocity IP Forwarding Support +- // CraftBukkit start +- // Paper start - Cache authenticator threads +- authenticatorPool.execute(() -> { ++ // DivineMC start - Async Join Thread ++ org.bxteam.divinemc.server.network.AsyncJoinHandler.runAsync(() -> { + try { + GameProfile gameprofile = ServerLoginPacketListenerImpl.this.createOfflineProfile(ServerLoginPacketListenerImpl.this.requestedUsername); // Spigot + + gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent + ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); +- ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); ++ return gameprofile; + } catch (Exception ex) { + ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!"); + ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + ServerLoginPacketListenerImpl.this.requestedUsername, ex); ++ return null; ++ } ++ }, (gameprofile) -> { ++ if (gameprofile != null) { ++ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); + } + }); +- // Paper end - Cache authenticator threads +- // CraftBukkit end ++ // DivineMC end - Async Join Thread + } + } + } +@@ -259,7 +262,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + } + + // Paper start - Cache authenticator threads +- authenticatorPool.execute(new Runnable() { ++ // DivineMC start - Async Join Thread ++ org.bxteam.divinemc.server.network.AsyncJoinHandler.runAsync(new Runnable() { + @Override + public void run() { + String string1 = Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized"); +@@ -410,16 +414,23 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, + //TODO Update handling for lazy sessions, might not even have to do anything? + + // Proceed with login +- authenticatorPool.execute(() -> { ++ // DivineMC start - Async Join Thread ++ org.bxteam.divinemc.server.network.AsyncJoinHandler.runAsync(() -> { + try { + final GameProfile gameprofile = this.callPlayerPreLoginEvents(this.authenticatedProfile); + ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); +- ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); ++ return gameprofile; + } catch (Exception ex) { + disconnect("Failed to verify username!"); + server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + this.authenticatedProfile.getName(), ex); ++ return null; ++ } ++ }, (gameprofile) -> { ++ if (gameprofile != null) { ++ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); + } + }); ++ // DivineMC end - Async Join Thread + return; + } + // Paper end - Add Velocity IP Forwarding Support diff --git a/divinemc-server/minecraft-patches/features/0091-Shutdown-executors.patch b/divinemc-server/minecraft-patches/features/0091-Shutdown-executors.patch new file mode 100644 index 0000000..cd6e003 --- /dev/null +++ b/divinemc-server/minecraft-patches/features/0091-Shutdown-executors.patch @@ -0,0 +1,18 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NONPLAYT <76615486+NONPLAYT@users.noreply.github.com> +Date: Thu, 10 Jul 2025 17:39:29 +0300 +Subject: [PATCH] Shutdown executors + + +diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java +index 6361d8abdeb045923e8ce64f02cbb7a9ed949d1e..b31a4edee0616a63026f7a4335205f2d99d2f641 100644 +--- a/net/minecraft/server/MinecraftServer.java ++++ b/net/minecraft/server/MinecraftServer.java +@@ -1081,6 +1081,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop queue(@NotNull AsyncPath path) { - return CompletableFuture.runAsync(path::process, pathProcessingExecutor) + return CompletableFuture.runAsync(path::process, PATH_PROCESSING_EXECUTOR) .orTimeout(60L, TimeUnit.SECONDS) .exceptionally(throwable -> { if (throwable instanceof TimeoutException e) { diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java index 6428810..038ebdf 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/entity/tracking/MultithreadedTracker.java @@ -29,7 +29,7 @@ public class MultithreadedTracker { private static final Logger LOGGER = LogManager.getLogger(THREAD_PREFIX); private static long lastWarnMillis = System.currentTimeMillis(); - private static final ThreadPoolExecutor TRACKER_EXECUTOR = new ThreadPoolExecutor( + public static final ThreadPoolExecutor TRACKER_EXECUTOR = new ThreadPoolExecutor( getCorePoolSize(), getMaxPoolSize(), getKeepAliveTime(), TimeUnit.SECONDS, diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/server/network/AsyncJoinHandler.java b/divinemc-server/src/main/java/org/bxteam/divinemc/server/network/AsyncJoinHandler.java new file mode 100644 index 0000000..9f62666 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/server/network/AsyncJoinHandler.java @@ -0,0 +1,102 @@ +package org.bxteam.divinemc.server.network; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import net.minecraft.server.MinecraftServer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bxteam.divinemc.config.DivineConfig; +import org.bxteam.divinemc.spark.ThreadDumperRegistry; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Supplier; + +public class AsyncJoinHandler { + private static final String THREAD_PREFIX = "Async Join Thread"; + public static final Logger LOGGER = LogManager.getLogger(AsyncJoinHandler.class.getSimpleName()); + public static ExecutorService JOIN_EXECUTOR; + + private static boolean enabled = false; + private static int threadCount = 2; + + /** + * Initialize the AsyncJoinHandler with configuration settings + */ + public static void init(boolean enabled, int threadCount) { + AsyncJoinHandler.enabled = enabled; + AsyncJoinHandler.threadCount = Math.max(1, threadCount); + + if (enabled) { + if (JOIN_EXECUTOR != null) { + JOIN_EXECUTOR.shutdown(); + } + + JOIN_EXECUTOR = org.bxteam.divinemc.config.DivineConfig.PerformanceCategory.virtualThreadsEnabled && + DivineConfig.AsyncCategory.asyncJoinUseVirtualThreads + ? Executors.newVirtualThreadPerTaskExecutor() + : Executors.newFixedThreadPool( + threadCount, + new ThreadFactoryBuilder() + .setNameFormat(THREAD_PREFIX) + .setDaemon(true) + .build() + ); + + ThreadDumperRegistry.REGISTRY.add(THREAD_PREFIX); + + LOGGER.info("Initialized AsyncJoinHandler with {} threads", threadCount); + } + } + + /** + * Execute a potentially blocking task asynchronously + * + * @param task The task to run asynchronously + * @param callback The callback to execute on the main thread when the task completes + * @param The return type of the task + */ + public static void runAsync(Supplier task, java.util.function.Consumer callback) { + if (!enabled || JOIN_EXECUTOR == null) { + T result = task.get(); + callback.accept(result); + return; + } + + CompletableFuture.supplyAsync(task, JOIN_EXECUTOR) + .thenAccept(result -> { + MinecraftServer.getServer().execute(() -> callback.accept(result)); + }) + .exceptionally(ex -> { + LOGGER.error("Error during async join operation", ex); + return null; + }); + } + + /** + * Execute a potentially blocking task asynchronously without a result + * + * @param asyncTask The task to run asynchronously + */ + public static void runAsync(Runnable asyncTask) { + if (!enabled || JOIN_EXECUTOR == null) { + asyncTask.run(); + return; + } + + CompletableFuture.runAsync(asyncTask, JOIN_EXECUTOR) + .thenRun(() -> MinecraftServer.getServer().execute(asyncTask)) + .exceptionally(ex -> { + LOGGER.error("Error during async join operation", ex); + return null; + }); + } + + /** + * Get the executor service for async join operations + */ + public static Executor getExecutor() { + return enabled && JOIN_EXECUTOR != null ? JOIN_EXECUTOR : Runnable::run; + } +} diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/AsyncProcessor.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/AsyncProcessor.java index fc19155..042ffcb 100644 --- a/divinemc-server/src/main/java/org/bxteam/divinemc/util/AsyncProcessor.java +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/AsyncProcessor.java @@ -1,61 +1,62 @@ package org.bxteam.divinemc.util; -import ca.spottedleaf.moonrise.common.util.TickThread; +import it.unimi.dsi.fastutil.PriorityQueue; +import it.unimi.dsi.fastutil.PriorityQueues; +import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; +import net.minecraft.Util; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bxteam.divinemc.spark.ThreadDumperRegistry; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; +import java.util.NoSuchElementException; +import java.util.concurrent.locks.LockSupport; -public class AsyncProcessor { +public class AsyncProcessor implements Runnable { private static final Logger LOGGER = LogManager.getLogger(AsyncProcessor.class); - private final BlockingQueue taskQueue; - private final Thread workerThread; - private volatile boolean isRunning; + public final Thread thread; + private final PriorityQueue jobs = PriorityQueues.synchronize(new ObjectArrayFIFOQueue<>()); + private volatile boolean killswitch = false; public AsyncProcessor(String threadName) { - this.taskQueue = new LinkedBlockingQueue<>(); - this.isRunning = true; + this.thread = Thread.ofPlatform() + .name(threadName) + .priority(Thread.NORM_PRIORITY - 1) + .daemon(false) + .uncaughtExceptionHandler(Util::onThreadException) + .unstarted(this); + } - this.workerThread = new TickThread(() -> { - while (isRunning || !taskQueue.isEmpty()) { + public void start() { + thread.start(); + ThreadDumperRegistry.REGISTRY.add(thread.getName()); + } + + public void join(long millis) throws InterruptedException { + killswitch = true; + LockSupport.unpark(thread); + thread.join(millis); + } + + public void submit(Runnable runnable) { + jobs.enqueue(runnable); + LockSupport.unpark(thread); + } + + @Override + public void run() { + while (!killswitch) { + try { + Runnable runnable; try { - Runnable task = taskQueue.take(); - task.run(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } catch (Exception e) { - LOGGER.error("An unexpected error occurred when running async processor: {}", e.getMessage(), e); + runnable = jobs.dequeue(); + } catch (NoSuchElementException e) { + LockSupport.park(); + continue; } + runnable.run(); + } catch (Exception e) { + LOGGER.error("Failed to execute async job for thread {}", thread.getName(), e); } - }, threadName); - - ThreadDumperRegistry.REGISTRY.add(threadName); - this.workerThread.start(); - } - - public void submit(Runnable task) { - if (!isRunning) { - throw new IllegalStateException("AsyncExecutor is not running."); } - - taskQueue.offer(task); - } - - public void shutdown() { - isRunning = false; - workerThread.interrupt(); - } - - public void shutdownNow() { - isRunning = false; - workerThread.interrupt(); - taskQueue.clear(); - } - - public boolean isRunning() { - return isRunning; } } diff --git a/divinemc-server/src/main/java/org/bxteam/divinemc/util/ExecutorShutdown.java b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ExecutorShutdown.java new file mode 100644 index 0000000..c0296b8 --- /dev/null +++ b/divinemc-server/src/main/java/org/bxteam/divinemc/util/ExecutorShutdown.java @@ -0,0 +1,51 @@ +package org.bxteam.divinemc.util; + +import net.minecraft.server.MinecraftServer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bxteam.divinemc.entity.pathfinding.AsyncPathProcessor; +import org.bxteam.divinemc.entity.tracking.MultithreadedTracker; +import org.bxteam.divinemc.server.network.AsyncJoinHandler; +import java.util.concurrent.TimeUnit; + +@SuppressWarnings("ConstantValue") +public class ExecutorShutdown { + public static final Logger LOGGER = LogManager.getLogger(ExecutorShutdown.class.getSimpleName()); + + public static void shutdown(MinecraftServer server) { + if (server.mobSpawnExecutor != null && server.mobSpawnExecutor.thread.isAlive()) { + LOGGER.info("Shutting down mob spawn executor..."); + + try { + server.mobSpawnExecutor.join(3000L); + } catch (InterruptedException ignored) { } + } + + if (MultithreadedTracker.TRACKER_EXECUTOR != null) { + LOGGER.info("Shutting down mob tracker executor..."); + MultithreadedTracker.TRACKER_EXECUTOR.shutdown(); + + try { + MultithreadedTracker.TRACKER_EXECUTOR.awaitTermination(10L, TimeUnit.SECONDS); + } catch (InterruptedException ignored) { } + } + + if (AsyncPathProcessor.PATH_PROCESSING_EXECUTOR != null) { + LOGGER.info("Shutting down mob pathfinding processing executor..."); + AsyncPathProcessor.PATH_PROCESSING_EXECUTOR.shutdown(); + + try { + AsyncPathProcessor.PATH_PROCESSING_EXECUTOR.awaitTermination(10L, TimeUnit.SECONDS); + } catch (InterruptedException ignored) { } + } + + if (AsyncJoinHandler.JOIN_EXECUTOR != null) { + LOGGER.info("Shutting down async join executor..."); + AsyncJoinHandler.JOIN_EXECUTOR.shutdown(); + + try { + AsyncJoinHandler.JOIN_EXECUTOR.awaitTermination(10L, TimeUnit.SECONDS); + } catch (InterruptedException ignored) { } + } + } +}