package net.minecraft.server.dedicated; import com.google.common.collect.Streams; import com.mojang.logging.LogUtils; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.nio.file.Path; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; import java.util.stream.Collectors; import net.minecraft.CrashReport; import net.minecraft.CrashReportCategory; import net.minecraft.CrashReportDetail; import net.minecraft.ReportType; import net.minecraft.Util; import net.minecraft.server.Bootstrap; import net.minecraft.util.TimeUtil; import net.minecraft.world.level.GameRules; import org.slf4j.Logger; public class ServerWatchdog implements Runnable { private static final Logger LOGGER = LogUtils.getLogger(); private static final long MAX_SHUTDOWN_TIME = 10000L; private static final int SHUTDOWN_STATUS = 1; private final DedicatedServer server; private final long maxTickTimeNanos; public ServerWatchdog(DedicatedServer server) { this.server = server; this.maxTickTimeNanos = server.getMaxTickLength() * TimeUtil.NANOSECONDS_PER_MILLISECOND; } public void run() { while (this.server.isRunning()) { long l = this.server.getNextTickTime(); long m = Util.getNanos(); long n = m - l; if (n > this.maxTickTimeNanos) { LOGGER.error( LogUtils.FATAL_MARKER, "A single server tick took {} seconds (should be max {})", String.format(Locale.ROOT, "%.2f", (float)n / (float)TimeUtil.NANOSECONDS_PER_SECOND), String.format(Locale.ROOT, "%.2f", this.server.tickRateManager().millisecondsPerTick() / (float)TimeUtil.MILLISECONDS_PER_SECOND) ); LOGGER.error(LogUtils.FATAL_MARKER, "Considering it to be crashed, server will forcibly shutdown."); CrashReport crashReport = createWatchdogCrashReport("Watching Server", this.server.getRunningThread().threadId()); this.server.fillSystemReport(crashReport.getSystemReport()); CrashReportCategory crashReportCategory = crashReport.addCategory("Performance stats"); crashReportCategory.setDetail( "Random tick rate", (CrashReportDetail)(() -> this.server.getWorldData().getGameRules().getRule(GameRules.RULE_RANDOMTICKING).toString()) ); crashReportCategory.setDetail( "Level stats", (CrashReportDetail)(() -> (String)Streams.stream(this.server.getAllLevels()) .map(serverLevel -> serverLevel.dimension().location() + ": " + serverLevel.getWatchdogStats()) .collect(Collectors.joining(",\n"))) ); Bootstrap.realStdoutPrintln("Crash report:\n" + crashReport.getFriendlyReport(ReportType.CRASH)); Path path = this.server.getServerDirectory().resolve("crash-reports").resolve("crash-" + Util.getFilenameFormattedDateTime() + "-server.txt"); if (crashReport.saveToFile(path, ReportType.CRASH)) { LOGGER.error("This crash report has been saved to: {}", path.toAbsolutePath()); } else { LOGGER.error("We were unable to save this crash report to disk."); } this.exit(); } try { Thread.sleep((l + this.maxTickTimeNanos - m) / TimeUtil.NANOSECONDS_PER_MILLISECOND); } catch (InterruptedException var10) { } } } public static CrashReport createWatchdogCrashReport(String title, long threadId) { ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true); StringBuilder stringBuilder = new StringBuilder(); Error error = new Error("Watchdog"); for (ThreadInfo threadInfo : threadInfos) { if (threadInfo.getThreadId() == threadId) { error.setStackTrace(threadInfo.getStackTrace()); } stringBuilder.append(threadInfo); stringBuilder.append("\n"); } CrashReport crashReport = new CrashReport(title, error); CrashReportCategory crashReportCategory = crashReport.addCategory("Thread Dump"); crashReportCategory.setDetail("Threads", stringBuilder); return crashReport; } private void exit() { try { Timer timer = new Timer(); timer.schedule(new TimerTask() { public void run() { Runtime.getRuntime().halt(1); } }, 10000L); System.exit(1); } catch (Throwable var2) { Runtime.getRuntime().halt(1); } } }