From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Martijn Muijsers Date: Fri, 3 Feb 2023 23:52:49 +0100 Subject: [PATCH] Watch for blocking base threads License: AGPL-3.0 (https://www.gnu.org/licenses/agpl-3.0.html) Gale - https://galemc.org diff --git a/build.gradle.kts b/build.gradle.kts index 00508c2b7a0aa9ecdd0c8709a559f26de17a0004..e01986477360b1dbe991af6667e726e8ac656246 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,7 @@ dependencies { exclude("io.papermc.paper", "paper-api") } // Gale end - project setup + implementation("io.projectreactor.tools:blockhound:1.0.7.RELEASE") // Gale - base thread pool - watch for blocking base threads // Paper start implementation("org.jline:jline-terminal-jansi:3.21.0") implementation("net.minecrell:terminalconsoleappender:1.3.0") @@ -169,11 +170,13 @@ fun TaskContainer.registerRunTask( if (providers.gradleProperty("paper.runDisableWatchdog").getOrElse("false") == "true") { systemProperty("disable.watchdog", true) } + systemProperty("gale.detect.thread.blocks", true) // Gale - base thread pool - watch for blocking base threads val memoryGb = providers.gradleProperty("paper.runMemoryGb").getOrElse("2") val modifiedJvmArgs = jvmArgs ?: arrayListOf() modifiedJvmArgs.addAll(listOf("-Xms${memoryGb}G", "-Xmx${memoryGb}G")) modifiedJvmArgs.add("--add-modules=jdk.incubator.vector") // Gale - Pufferfish - SIMD support + modifiedJvmArgs.add("-XX:+AllowRedefinitionToAddDeleteMethods") // Gale - base thread pool - watch for blocking base threads jvmArgs = modifiedJvmArgs doFirst { diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java index fab5d1c1531fa89113bef6d17df8437b0aec4582..95d8715928a0ddccdabcb76090a3b5bdd143c5a5 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java @@ -4,18 +4,20 @@ import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Date; -import java.util.List; +import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; import joptsimple.OptionParser; import joptsimple.OptionSet; +import net.minecraft.server.MinecraftServer; import net.minecraft.util.ExceptionCollector; import net.minecraft.world.level.lighting.LayerLightEventListener; import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper +import org.galemc.gale.executor.thread.BaseThread; +import org.spigotmc.WatchdogThread; +import reactor.blockhound.BlockHound; public class Main { public static boolean useJline = true; @@ -48,6 +50,49 @@ public class Main { // Gale end - include time in startup logs public static void main(String[] args) { + // Gale start - base thread pool - watch for blocking base threads + if (Boolean.getBoolean("gale.detect.thread.blocks")) { + printlnStartupInfoToSystemOut("Initializing blocking base thread detection..."); + try { + var builder = BlockHound.builder(); + // Mark base threads as intended to be non-blocking (except the WatchdogThread, which obviously sleeps until needed) + builder.nonBlockingThreadPredicate(currentPredicate -> currentPredicate.or(thread -> thread instanceof BaseThread && !(thread instanceof WatchdogThread))); + // Set the callback when a base thread blocks + builder.blockingMethodCallback(blockingMethod -> { + if (MinecraftServer.SERVER == null) { + // Allow blocking before the server has finished initializing + return; + } + String message = "A base thread (" + Thread.currentThread() + ") started blocking:"; + if (MinecraftServer.LOGGER != null) { + MinecraftServer.LOGGER.error(message); + } else { + printlnStartupErrorToSystemOut(message); + } + new Error(blockingMethod.toString()).printStackTrace(); + }); + // Allow busy waiting + var blockingMethodsField = builder.getClass().getDeclaredField("blockingMethods"); + blockingMethodsField.setAccessible(true); + Map>> blockingMethods = (Map>>) blockingMethodsField.get(builder); + Map> threadBlockingMethods = blockingMethods.get("java/lang/Thread"); + threadBlockingMethods.remove("onSpinWait"); + threadBlockingMethods.remove("yield"); + // Allow base threads to block in the intended way + builder.allowBlockingCallsInside("org.galemc.gale.executor.thread.BaseThread", "waitUntilSignalled"); + // Allow the ServerThread to block during initialization + builder.allowBlockingCallsInside("net.minecraft.server.dedicated.DedicatedServer", "initServer"); + // Install BlockHound + builder.install(); + printlnStartupInfoToSystemOut("Blocking base thread detection is enabled."); + } catch (Exception e) { + // BlockHound instrumentation failed, which probably means the needed JVM flag is missing + printlnStartupInfoToSystemOut("Blocking base thread detection is disabled."); + printlnStartupInfoToSystemOut("When it is enabled, unexpected thread blocks can be automatically resolved to improve performance."); + printlnStartupInfoToSystemOut("To enable it, add \"-XX:+AllowRedefinitionToAddDeleteMethods\" to your startup flags, BEFORE the \"-jar\"."); + } + } + // Gale end - base thread pool - watch for blocking base threads // Paper start final String warnWhenLegacyFormattingDetected = String.join(".", "net", "kyori", "adventure", "text", "warnWhenLegacyFormattingDetected"); if (false && System.getProperty(warnWhenLegacyFormattingDetected) == null) {