All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.utils.Watchdog Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
package cn.nukkit.utils;

import cn.nukkit.Server;
import lombok.extern.log4j.Log4j2;

import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;

@Log4j2
public class Watchdog extends Thread {

    private final Server server;
    private final long time;
    public volatile boolean running;
    private boolean responding = true;
    private Thread forcedFinalizer;
    private boolean warnedAboutFinalizer;

    public Watchdog(Server server, long time) {
        this.server = server;
        this.time = time;
        this.running = true;
        this.setName("Watchdog");
        this.setDaemon(true);
        this.setPriority(Thread.MIN_PRIORITY);
    }

    public void kill() {
        running = false;
        interrupt();
    }

    private void checkFinalizer() {
        if (forcedFinalizer != null && forcedFinalizer.isAlive()) {
            StringBuilder sb = new StringBuilder("--------- The finalizer thread didn't complete in time! ---------").append('\n')
                    .append("This detection means that the finalizer thread may be stuck and").append('\n')
                    .append("RAM memory might be leaking!").append('\n')
                    .append(" - https://github.com/PowerNukkitX/PowerNukkitX/issues/new").append('\n')
                    .append("---------------- ForcedFinalizer ----------------").append('\n');
            dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(forcedFinalizer.getId(), Integer.MAX_VALUE), sb);
            sb.append("-------------------------------------------------");
            log.fatal(sb.toString());
            warnedAboutFinalizer = true;
        } else {
            if (warnedAboutFinalizer) {
                log.warn("The ForcedFinalizer has finished");
                warnedAboutFinalizer = false;
            }
            forcedFinalizer = new Thread(() -> {
                log.trace("Forcing finalization");
                System.runFinalization();
                log.trace("Forced finalization completed");
            });
            forcedFinalizer.setName("ForcedFinalizer");
            forcedFinalizer.setDaemon(true);
            forcedFinalizer.start();
        }
    }

    @Override
    public void run() {
        while (this.running) {
            checkFinalizer();
            long current = server.getNextTick();
            if (current != 0) {
                var now = System.currentTimeMillis();
                long diff = now - current;
                if (!responding && diff > time * 2) {
                    System.exit(1); // Kill the server if it gets stuck on shutdown
                }

                if (diff <= time) {
                    responding = true;
                } else if (responding && now - server.getBusyingTime() < 60) {
                    StringBuilder builder = new StringBuilder(
                            "--------- Server stopped responding --------- (" + Math.round(diff / 1000d) + "s)").append('\n')
                            .append("Please report this to PowerNukkitX:").append('\n')
                            .append(" - https://github.com/PowerNukkitX/PowerNukkitX/issues/new").append('\n')
                            .append("---------------- Main thread ----------------").append('\n');

                    dumpThread(ManagementFactory.getThreadMXBean().getThreadInfo(this.server.getPrimaryThread().getId(), Integer.MAX_VALUE), builder);

                    builder.append("---------------- All threads ----------------").append('\n');
                    ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);
                    for (int i = 0; i < threads.length; i++) {
                        if (i != 0) builder.append("------------------------------").append('\n');
                        dumpThread(threads[i], builder);
                    }
                    builder.append("---------------------------------------------").append('\n');
                    log.fatal(builder.toString());
                    responding = false;
                    this.server.forceShutdown();
                }
            }
            try {
                sleep(Math.max(time / 4, 1000));
            } catch (InterruptedException interruption) {
                log.fatal("The Watchdog Thread has been interrupted and is no longer monitoring the server state", interruption);
                running = false;
                return;
            }
        }
        log.warn("Watchdog was stopped");
    }

    private static void dumpThread(ThreadInfo thread, StringBuilder builder) {
        if (thread == null) {
            builder.append("Attempted to dump a null thread!").append('\n');
            return;
        }
        builder.append("Current Thread: ").append(thread.getThreadName()).append('\n');
        builder.append("\tPID: ").append(thread.getThreadId()).append(" | Suspended: ").append(thread.isSuspended()).append(" | Native: ").append(thread.isInNative()).append(" | State: ").append(thread.getThreadState()).append('\n');
        // Monitors
        if (thread.getLockedMonitors().length != 0) {
            builder.append("\tThread is waiting on monitor(s):").append('\n');
            for (MonitorInfo monitor : thread.getLockedMonitors()) {
                builder.append("\t\tLocked on:").append(monitor.getLockedStackFrame()).append('\n');
            }
        }

        builder.append("\tStack:").append('\n');
        for (var stack : thread.getStackTrace()) {
            builder.append("\t\t").append(stack).append('\n');
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy