diff --git a/mirai-console/frontend/mirai-console-terminal/src/ConsoleThread.kt b/mirai-console/frontend/mirai-console-terminal/src/ConsoleThread.kt index 745afba0ff..9ad0fe4ad5 100644 --- a/mirai-console/frontend/mirai-console-terminal/src/ConsoleThread.kt +++ b/mirai-console/frontend/mirai-console-terminal/src/ConsoleThread.kt @@ -69,7 +69,7 @@ internal fun startupConsoleThread() { } catch (e: CancellationException) { return@launch } catch (e: UserInterruptException) { - BuiltInCommands.StopCommand.run { ConsoleCommandSender.handle() } + signalHandler("INT") return@launch } catch (eof: EndOfFileException) { consoleLogger.warning("Closing input service...") diff --git a/mirai-console/frontend/mirai-console-terminal/src/MiraiConsoleTerminalLoader.kt b/mirai-console/frontend/mirai-console-terminal/src/MiraiConsoleTerminalLoader.kt index 92998e6fca..ccd8ea90d4 100644 --- a/mirai-console/frontend/mirai-console-terminal/src/MiraiConsoleTerminalLoader.kt +++ b/mirai-console/frontend/mirai-console-terminal/src/MiraiConsoleTerminalLoader.kt @@ -52,15 +52,18 @@ object MiraiConsoleTerminalLoader { startAsDaemon() try { runBlocking { - MiraiConsole.job.invokeOnCompletion { - Thread.sleep(1000) // 保证错误信息打印完全 - exitProcess(0) + MiraiConsole.job.invokeOnCompletion { err -> + if (err != null) { + Thread.sleep(1000) // 保证错误信息打印完全 + } } MiraiConsole.job.join() } } catch (e: CancellationException) { // ignored } + // Avoid plugin started some non-daemon threads + exitProcessAndForceHalt(0) } @ConsoleTerminalExperimentalApi @@ -207,7 +210,8 @@ private fun initSignalHandler(): (String) -> Unit { MiraiConsole.job.join() delay(15000) - exitProcess(-5) // Force kill process if plugins started non-daemon threads + // Force kill process if plugins started non-daemon threads + exitProcessAndForceHalt(-5) } } } @@ -220,6 +224,38 @@ internal fun registerSignalHandler() { shutdownSignals.forEach { reg(it) } } +internal fun exitProcessAndForceHalt(code: Int): Nothing { + MiraiConsole.mainLogger.debug { "[exitProcessAndForceHalt] called with code $code" } + + val exitFuncName = arrayOf("exit", "halt") + val shutdownClasses = arrayOf("java.lang.System", "java.lang.Runtime", "java.lang.Shutdown") + val isShutdowning = Thread.getAllStackTraces().asSequence().flatMap { + it.value.asSequence() + }.any { stackTrace -> + stackTrace.className in shutdownClasses && stackTrace.methodName in exitFuncName + } + MiraiConsole.mainLogger.debug { "[exitProcessAndForceHalt] isShutdowning = $isShutdowning" } + + val task = Runnable { + Thread.sleep(15000L) + runCatching { net.mamoe.mirai.console.internal.shutdown.ShutdownDaemon.dumpCrashReport(true) } + val fc = when (code) { + 0 -> 5784171 + else -> code + } + + MiraiConsole.mainLogger.debug { "[exitProcessAndForceHalt] timed out, force halt with code $fc" } + Runtime.getRuntime().halt(fc) + } + if (isShutdowning) { + task.run() + error("Runtime.halt returned normally, while it was supposed to halt JVM.") + } else { + Thread(task, "Mirai Console Force Halt Daemon").start() + exitProcess(code) + } +} + internal fun overrideSTD(terminal: MiraiConsoleImplementation) { if (ConsoleTerminalSettings.noConsole) { SystemOutputPrintStream // Avoid StackOverflowError when launch with no console mode