diff --git a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java index 3247ea38..a81291e2 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java @@ -20,11 +20,15 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.common.ObfuscationReflectionHelper; +import net.minecraftforge.fml.event.server.FMLServerStartedEvent; +import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.network.NetworkEvent; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; +import org.embeddedt.modernfix.core.config.ModernFixConfig; import org.embeddedt.modernfix.load.LoadEvents; import org.embeddedt.modernfix.packet.EntityIDSyncPacket; import org.embeddedt.modernfix.screen.DeferredLevelLoadingScreen; +import org.embeddedt.modernfix.world.IntegratedWatchdog; import java.lang.management.ManagementFactory; import java.lang.reflect.Field; @@ -219,4 +223,13 @@ public class ModernFixClient { context.get().setPacketHandled(true); } + + @SubscribeEvent + public void onServerStarted(FMLServerStartingEvent event) { + if(ModernFixConfig.INTEGRATED_SERVER_WATCHDOG.get()) { + IntegratedWatchdog watchdog = new IntegratedWatchdog(event.getServer()); + watchdog.start(); + } + + } } diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java index 92500015..56142410 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixConfig.java @@ -26,6 +26,7 @@ public class ModernFixConfig { public static ForgeConfigSpec.BooleanValue ENABLE_DEBUG_RELOADER; public static ForgeConfigSpec.BooleanValue REBUILD_BLOCKSTATES_ASYNC; + public static ForgeConfigSpec.BooleanValue INTEGRATED_SERVER_WATCHDOG; public static Set jeiPluginBlacklist; @@ -43,6 +44,9 @@ public class ModernFixConfig { REBUILD_BLOCKSTATES_ASYNC = COMMON_BUILDER .comment("Rebuild blockstate cache asynchronously. Should work with most mods, but can be disabled.") .define("rebuild_blockstate_cache_async", true); + INTEGRATED_SERVER_WATCHDOG = COMMON_BUILDER + .comment("Automatically output a thread dump if the integrated server spends too long on one tick") + .define("integrated_server_watchdog", true); } static { diff --git a/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java b/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java new file mode 100644 index 00000000..d2d8b4df --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/world/IntegratedWatchdog.java @@ -0,0 +1,61 @@ +package org.embeddedt.modernfix.world; + +import net.minecraft.DefaultUncaughtExceptionHandlerWithName; +import net.minecraft.Util; +import net.minecraft.server.MinecraftServer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.concurrent.TimeUnit; + +public class IntegratedWatchdog extends Thread { + private static final Logger LOGGER = LogManager.getLogger(); + + private final MinecraftServer server; + + private static final long MAX_TICK_DELTA = 40*1000; + + public IntegratedWatchdog(MinecraftServer server) { + this.server = server; + this.setDaemon(true); + this.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(LOGGER)); + this.setName("ModernFix integrated server watchdog"); + } + + public void run() { + while(server.isRunning()) { + long nextTick = this.server.getNextTickTime(); + long curTime = Util.getMillis(); + if((curTime - nextTick) > MAX_TICK_DELTA) { + LOGGER.error("A single server tick has taken {}, more than {} milliseconds", (nextTick - curTime), MAX_TICK_DELTA); + ThreadMXBean threadmxbean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] athreadinfo = threadmxbean.dumpAllThreads(true, true); + StringBuilder sb = new StringBuilder(); + sb.append("Thread Dump:\n"); + for(ThreadInfo threadinfo : athreadinfo) { + sb.append(threadinfo); + StackTraceElement[] elements = threadinfo.getStackTrace(); + if(elements.length > 8) { + sb.append("extended trace:\n"); + for(int i = 8; i < elements.length; i++) { + sb.append("\tat "); + sb.append(elements[i]); + sb.append('\n'); + } + } + sb.append('\n'); + } + LOGGER.error(sb); + nextTick = 0; + curTime = 0; + } + try { + Thread.sleep(nextTick + MAX_TICK_DELTA - curTime); + } catch(InterruptedException ignored) { + } + } + } +}