'use strict'; const DEFAULT_GRACE_MS = 15000; const CLEAN_SHUTDOWN = /\[(?:Render thread|Client thread)\/INFO\](?:\s+\([^)]*\))?:\s*Stopping!\s*$/; const FATAL_OUTPUT = [ /---- Minecraft Crash Report ----/i, /\b(?:Encountered an unexpected exception|Unreported exception thrown|Reported exception thrown)\b/i, /\b(?:This crash report has been saved to|Saving crash report to)\b/i, /\bA fatal error has been detected by the Java Runtime Environment\b/i, /Exception in thread "(?:Render thread|Client thread|main)"/i, /\[(?:Render thread|Client thread|main)\/FATAL\]/i, ]; function createProcessWatchdog(options) { const graceMs = options.graceMs || DEFAULT_GRACE_MS; const schedule = options.setTimer || setTimeout; const cancel = options.clearTimer || clearTimeout; const buffers = { stdout: '', stderr: '' }; let timer = null; let armed = false; let crashed = false; let ended = false; let forced = false; // bleibt true, sobald einmal ein regulärer Shutdown ("Stopping!") gesehen // wurde – im Gegensatz zu `armed` wird das NICHT durch markCrash() // zurückgesetzt, damit ein später vom internen Watchdog erzwungener Halt // weiterhin als "war ein regulärer Shutdown" erkennbar bleibt let sawCleanShutdown = false; function clear() { if (timer !== null) cancel(timer); timer = null; armed = false; } function markCrash() { crashed = true; clear(); } function scheduleKill() { if (timer !== null) cancel(timer); timer = schedule(() => { timer = null; if (ended || crashed || !armed || !options.isRunning()) return; try { forced = options.kill() === true; } catch { forced = false; } if (forced && options.onForce) options.onForce(graceMs); else if (!forced && options.isRunning() && options.onError) options.onError(); }, graceMs); if (timer && typeof timer.unref === 'function') timer.unref(); } function inspectLine(line) { const isCleanShutdown = CLEAN_SHUTDOWN.test(line); if (isCleanShutdown) sawCleanShutdown = true; if (FATAL_OUTPUT.some((pattern) => pattern.test(line))) { markCrash(); return; } if (ended || crashed || armed || !isCleanShutdown) return; armed = true; scheduleKill(); } function feed(chunk, stream) { if (ended) return; const key = stream === 'stderr' ? 'stderr' : 'stdout'; const text = buffers[key] + String(chunk || ''); const lines = text.split(/\r?\n/); buffers[key] = lines.pop() || ''; for (const line of lines) inspectLine(line); if (armed && !crashed && !ended && lines.length) scheduleKill(); } function end() { ended = true; clear(); } return { feed, end, get armed() { return armed; }, get crashed() { return crashed; }, get forced() { return forced; }, get sawCleanShutdown() { return sawCleanShutdown; }, }; } module.exports = { DEFAULT_GRACE_MS, CLEAN_SHUTDOWN, FATAL_OUTPUT, createProcessWatchdog, };