/* * Decompiled with CFR 0.152. * * Could not load the following classes: * com.google.common.collect.Streams * com.mojang.logging.LogUtils * org.slf4j.Logger */ 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.ReportType; import net.minecraft.server.Bootstrap; import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.util.TimeUtil; import net.minecraft.util.Util; import net.minecraft.world.level.gamerules.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; } @Override public void run() { while (this.server.isRunning()) { long nextTickTimeNanos = this.server.getNextTickTime(); long currentTimeNanos = Util.getNanos(); long deltaNanos = currentTimeNanos - nextTickTimeNanos; if (deltaNanos > this.maxTickTimeNanos) { LOGGER.error(LogUtils.FATAL_MARKER, "A single server tick took {} seconds (should be max {})", (Object)String.format(Locale.ROOT, "%.2f", Float.valueOf((float)deltaNanos / (float)TimeUtil.NANOSECONDS_PER_SECOND)), (Object)String.format(Locale.ROOT, "%.2f", Float.valueOf(this.server.tickRateManager().millisecondsPerTick() / (float)TimeUtil.MILLISECONDS_PER_SECOND))); LOGGER.error(LogUtils.FATAL_MARKER, "Considering it to be crashed, server will forcibly shutdown."); CrashReport report = ServerWatchdog.createWatchdogCrashReport("Watching Server", this.server.getRunningThread().threadId()); this.server.fillSystemReport(report.getSystemReport()); CrashReportCategory serverStats = report.addCategory("Performance stats"); serverStats.setDetail("Random tick rate", () -> this.server.getWorldData().getGameRules().getAsString(GameRules.RANDOM_TICK_SPEED)); serverStats.setDetail("Level stats", () -> Streams.stream(this.server.getAllLevels()).map(level -> String.valueOf(level.dimension().identifier()) + ": " + level.getWatchdogStats()).collect(Collectors.joining(",\n"))); Bootstrap.realStdoutPrintln("Crash report:\n" + report.getFriendlyReport(ReportType.CRASH)); Path file = this.server.getServerDirectory().resolve("crash-reports").resolve("crash-" + Util.getFilenameFormattedDateTime() + "-server.txt"); if (report.saveToFile(file, ReportType.CRASH)) { LOGGER.error("This crash report has been saved to: {}", (Object)file.toAbsolutePath()); } else { LOGGER.error("We were unable to save this crash report to disk."); } this.exit(); } try { Thread.sleep((nextTickTimeNanos + this.maxTickTimeNanos - currentTimeNanos) / TimeUtil.NANOSECONDS_PER_MILLISECOND); } catch (InterruptedException interruptedException) {} } } public static CrashReport createWatchdogCrashReport(String message, long mainThreadId) { ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true); StringBuilder builder = new StringBuilder(); Error exception = new Error("Watchdog"); for (ThreadInfo threadInfo : threadInfos) { if (threadInfo.getThreadId() == mainThreadId) { exception.setStackTrace(threadInfo.getStackTrace()); } builder.append(threadInfo); builder.append("\n"); } CrashReport report = new CrashReport(message, exception); CrashReportCategory threadDump = report.addCategory("Thread Dump"); threadDump.setDetail("Threads", builder); return report; } private void exit() { try { Timer timer = new Timer(); timer.schedule(new TimerTask(this){ @Override public void run() { Runtime.getRuntime().halt(1); } }, 10000L); System.exit(1); } catch (Throwable ignored) { Runtime.getRuntime().halt(1); } } }