diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt index 4adc7e6bb..644df4076 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt @@ -108,7 +108,7 @@ public interface MiraiConsole : CoroutineScope { Bot(id, password) { fileBasedDeviceInfo() redirectNetworkLogToDirectory() - parentCoroutineContext = MiraiConsole.childScopeContext() + parentCoroutineContext = MiraiConsole.childScopeContext("Bot $id") this.loginSolver = MiraiConsoleImplementationBridge.createLoginSolver(id, this) configuration() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt index 0c1204cdb..98dc7e1df 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/BuiltInCommands.kt @@ -27,6 +27,7 @@ import net.mamoe.mirai.console.util.BotManager.INSTANCE.removeManager import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.contact.* +import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.message.nextMessageOrNull import net.mamoe.mirai.utils.secondsToMillis @@ -113,19 +114,27 @@ public object BuiltInCommands { @Handler public suspend fun CommandSender.handle() { - closingLock.withLock { - sendMessage("Stopping mirai-console") - kotlin.runCatching { - MiraiConsole.job.cancelAndJoin() - }.fold( - onSuccess = { sendMessage("mirai-console stopped successfully.") }, - onFailure = { - @OptIn(ConsoleInternalAPI::class) - MiraiConsole.mainLogger.error(it) - sendMessage(it.localizedMessage ?: it.message ?: it.toString()) - } - ) - } + kotlin.runCatching { + closingLock.withLock { + sendMessage("Stopping mirai-console") + kotlin.runCatching { + MiraiConsole.job.cancelAndJoin() + }.fold( + onSuccess = { + ignoreException { sendMessage("mirai-console stopped successfully.") } + }, + onFailure = { + @OptIn(ConsoleInternalAPI::class) + MiraiConsole.mainLogger.error(it) + ignoreException { + sendMessage( + it.localizedMessage ?: it.message ?: it.toString() + ) + } + } + ) + } + }.exceptionOrNull()?.let(MiraiConsole.mainLogger::error) exitProcess(0) } } @@ -158,6 +167,24 @@ public object BuiltInCommands { } } +internal inline fun ignoreException(block: () -> R): R? { + try { + return block() + } catch (e: Throwable) { + if (e is E) return null + throw e + } +} + +internal inline fun ignoreException(block: () -> Unit): Unit? { + try { + return block() + } catch (e: Throwable) { + if (e is E) return null + throw e + } +} + internal fun ContactOrBot.render(): String { return when (this) { is Bot -> "Bot $nick($id)" diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt index 0c9a05bb0..8bd3f3242 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/PluginDataHolder.kt @@ -28,6 +28,7 @@ public interface PluginDataHolder { /** * 保存时使用的分类名 */ + @ConsoleExperimentalAPI public val name: String } @@ -50,6 +51,7 @@ public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope { * @see LongRange Java 用户使用 [LongRange] 的构造器创建 * @see Long.rangeTo Kotlin 用户使用 [Long.rangeTo] 创建, 如 `3000..50000` */ + @ConsoleExperimentalAPI public val autoSaveIntervalMillis: LongRange } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt index be1b4dd65..d2f1ff495 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JarPluginLoaderImpl.kt @@ -44,7 +44,7 @@ internal object JarPluginLoaderImpl : get() = MiraiConsoleImplementationBridge.dataStorageForJarPluginLoader override val coroutineContext: CoroutineContext = - MiraiConsole.childScopeContext(CoroutineExceptionHandler { _, throwable -> + MiraiConsole.childScopeContext("JarPluginLoader", CoroutineExceptionHandler { _, throwable -> logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable) }) diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt index a1a3e90c1..dd0f43fc3 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt @@ -26,7 +26,6 @@ import java.io.InputStream import java.nio.file.Path import java.util.concurrent.locks.ReentrantLock import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext internal val T.job: Job where T : CoroutineScope, T : Plugin get() = this.coroutineContext[Job]!! @@ -119,14 +118,15 @@ internal abstract class JvmPluginInternal( // for future use @Suppress("PropertyName") - @JvmField - internal var _intrinsicCoroutineContext: CoroutineContext = EmptyCoroutineContext + internal val _intrinsicCoroutineContext: CoroutineContext by lazy { + CoroutineName("Plugin $name") + } @JvmField internal val coroutineContextInitializer = { CoroutineExceptionHandler { _, throwable -> logger.error(throwable) } .plus(parentCoroutineContext) - .plus(SupervisorJob(parentCoroutineContext[Job])) + .plus(NamedSupervisorJob("Plugin $name", parentCoroutineContext[Job])) .also { JarPluginLoaderImpl.coroutineContext[Job]!!.invokeOnCompletion { this.cancel() @@ -156,6 +156,16 @@ internal abstract class JvmPluginInternal( // endregion } +@Suppress("FunctionName") +internal class NamedSupervisorJob( + private val name: String, + parent: Job? = null +) : CompletableJob by SupervisorJob(parent) { + override fun toString(): String { + return "NamedSupervisorJob($name)" + } +} + internal inline fun AtomicLong.updateWhen(condition: (Long) -> Boolean, update: (Long) -> Long): Boolean { while (true) { val current = value diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt index c28349223..cd294f064 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/PluginManagerImpl.kt @@ -28,7 +28,7 @@ import java.io.File import java.nio.file.Path import java.util.concurrent.locks.ReentrantLock -internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsole.childScope() { +internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsole.childScope("PluginManager") { override val pluginsPath: Path = MiraiConsole.rootPath.resolve("plugins").apply { mkdir() } override val pluginsFolder: File = pluginsPath.toFile() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt index dcff96398..e15b1437b 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/BotManagerImpl.kt @@ -11,15 +11,16 @@ package net.mamoe.mirai.console.internal.util +import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.SupervisorJob import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.* import net.mamoe.mirai.console.data.PluginDataExtensions.mapKeys import net.mamoe.mirai.console.data.PluginDataExtensions.withEmptyDefault import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge +import net.mamoe.mirai.console.internal.plugin.NamedSupervisorJob import net.mamoe.mirai.console.util.BotManager import net.mamoe.mirai.contact.User import net.mamoe.mirai.utils.minutesToMillis @@ -52,14 +53,25 @@ internal object ManagersConfig : AutoSavePluginConfig() { } -internal fun CoroutineContext.overrideWithSupervisorJob(): CoroutineContext = this + SupervisorJob(this[Job]) -internal fun CoroutineScope.childScope(context: CoroutineContext = EmptyCoroutineContext): CoroutineScope = - CoroutineScope(this.childScopeContext(context)) +internal fun CoroutineContext.overrideWithSupervisorJob(name: String? = null): CoroutineContext = + this + NamedSupervisorJob(name ?: "", this[Job]) -internal fun CoroutineScope.childScopeContext(context: CoroutineContext = EmptyCoroutineContext): CoroutineContext = - this.coroutineContext.overrideWithSupervisorJob() + context +internal fun CoroutineScope.childScope( + name: String? = null, + context: CoroutineContext = EmptyCoroutineContext +): CoroutineScope = + CoroutineScope(this.childScopeContext(name, context)) -internal object ConsoleDataScope : CoroutineScope by MiraiConsole.childScope() { +internal fun CoroutineScope.childScopeContext( + name: String? = null, + context: CoroutineContext = EmptyCoroutineContext +): CoroutineContext = + this.coroutineContext.overrideWithSupervisorJob(name) + context.let { + if (name != null) it + CoroutineName(name) + else it + } + +internal object ConsoleDataScope : CoroutineScope by MiraiConsole.childScope("ConsoleDataScope") { private val data: Array = arrayOf() private val configs: Array = arrayOf(ManagersConfig) @@ -74,13 +86,13 @@ internal object ConsoleDataScope : CoroutineScope by MiraiConsole.childScope() { } internal object ConsoleBuiltInPluginDataHolder : AutoSavePluginDataHolder, - CoroutineScope by ConsoleDataScope.childScope() { + CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginDataHolder") { override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis override val name: String get() = "ConsoleBuiltIns" } internal object ConsoleBuiltInPluginConfigHolder : AutoSavePluginDataHolder, - CoroutineScope by ConsoleDataScope.childScope() { + CoroutineScope by ConsoleDataScope.childScope("ConsoleBuiltInPluginConfigHolder") { override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis override val name: String get() = "ConsoleBuiltIns" } diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/JavaPluginSchedulerImpl.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/JavaPluginSchedulerImpl.kt index ec532424f..fcb9ebde0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/JavaPluginSchedulerImpl.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/util/JavaPluginSchedulerImpl.kt @@ -11,6 +11,7 @@ package net.mamoe.mirai.console.internal.util import kotlinx.coroutines.* import kotlinx.coroutines.future.future +import net.mamoe.mirai.console.internal.plugin.NamedSupervisorJob import net.mamoe.mirai.console.plugin.jvm.JavaPluginScheduler import java.util.concurrent.Callable import java.util.concurrent.CompletableFuture @@ -20,7 +21,7 @@ import kotlin.coroutines.CoroutineContext internal class JavaPluginSchedulerImpl internal constructor(parentCoroutineContext: CoroutineContext) : CoroutineScope, JavaPluginScheduler { override val coroutineContext: CoroutineContext = - parentCoroutineContext + SupervisorJob(parentCoroutineContext[Job]) + parentCoroutineContext + NamedSupervisorJob(this.toString(), parentCoroutineContext[Job]) override fun repeating(intervalMs: Long, runnable: Runnable): Future { return this.future { diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt index 77d8c016e..c3609b36c 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt @@ -12,6 +12,7 @@ package net.mamoe.mirai.console.plugin.jvm import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal +import net.mamoe.mirai.console.util.ConsoleExperimentalAPI import net.mamoe.mirai.utils.minutesToMillis import net.mamoe.mirai.utils.secondsToMillis import kotlin.coroutines.CoroutineContext @@ -28,5 +29,6 @@ public abstract class AbstractJvmPlugin @JvmOverloads constructor( ) : JvmPlugin, JvmPluginInternal(parentCoroutineContext) { public final override val name: String get() = this.description.name + @ConsoleExperimentalAPI public override val autoSaveIntervalMillis: LongRange = 30.secondsToMillis..10.minutesToMillis } \ No newline at end of file diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt index 15e0ef628..8f6ecae2c 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt @@ -11,7 +11,7 @@ package net.mamoe.mirai.console.pure import kotlinx.coroutines.CancellationException import kotlinx.coroutines.cancel -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.launch import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.Command.Companion.primaryName @@ -22,71 +22,53 @@ import net.mamoe.mirai.console.util.ConsoleInternalAPI import net.mamoe.mirai.console.util.requestInput import net.mamoe.mirai.utils.DefaultLogger import org.jline.reader.UserInterruptException -import kotlin.concurrent.thread val consoleLogger by lazy { DefaultLogger("console") } @OptIn(ConsoleInternalAPI::class) internal fun startupConsoleThread() { - - val inputThread = thread(start = true, isDaemon = false, name = "Console Input") { - try { - runBlocking { - while (true) { - try { - val next = MiraiConsole.requestInput("").let { - when { - it.startsWith(CommandManager.commandPrefix) -> it - it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName - else -> CommandManager.commandPrefix + it - } - } - if (next.isBlank()) { - continue - } - // consoleLogger.debug("INPUT> $next") - val result = ConsoleCommandSenderImpl.executeCommand(next) - when (result.status) { - CommandExecuteStatus.SUCCESSFUL -> { - } - CommandExecuteStatus.EXECUTION_EXCEPTION -> { - result.exception?.let(consoleLogger::error) - } - CommandExecuteStatus.COMMAND_NOT_FOUND -> { - consoleLogger.warning("未知指令: ${result.commandName}, 输入 ? 获取帮助") - } - CommandExecuteStatus.PERMISSION_DENIED -> { - consoleLogger.warning("Permission denied.") - } - } - } catch (e: InterruptedException) { - return@runBlocking - } catch (e: CancellationException) { - return@runBlocking - } catch (e: UserInterruptException) { - MiraiConsole.cancel() - return@runBlocking - } catch (e: Throwable) { - consoleLogger.error("Unhandled exception", e) + MiraiConsole.launch { + while (true) { + try { + val next = MiraiConsole.requestInput("").let { + when { + it.startsWith(CommandManager.commandPrefix) -> it + it == "?" -> CommandManager.commandPrefix + BuiltInCommands.Help.primaryName + else -> CommandManager.commandPrefix + it } } + if (next.isBlank()) { + continue + } + // consoleLogger.debug("INPUT> $next") + val result = ConsoleCommandSenderImpl.executeCommand(next) + when (result.status) { + CommandExecuteStatus.SUCCESSFUL -> { + } + CommandExecuteStatus.EXECUTION_EXCEPTION -> { + result.exception?.let(consoleLogger::error) + } + CommandExecuteStatus.COMMAND_NOT_FOUND -> { + consoleLogger.warning("未知指令: ${result.commandName}, 输入 ? 获取帮助") + } + CommandExecuteStatus.PERMISSION_DENIED -> { + consoleLogger.warning("Permission denied.") + } + } + } catch (e: InterruptedException) { + return@launch + } catch (e: CancellationException) { + return@launch + } catch (e: UserInterruptException) { + MiraiConsole.cancel() + return@launch + } catch (e: Throwable) { + consoleLogger.error("Unhandled exception", e) } - } catch (e: InterruptedException) { - return@thread - } catch (e: CancellationException) { - return@thread - } catch (e: UserInterruptException) { - MiraiConsole.cancel() - return@thread - } catch (e: Throwable) { - consoleLogger.error("Unhandled exception", e) } } MiraiConsole.job.invokeOnCompletion { - runCatching { - inputThread.interrupt() - }.exceptionOrNull()?.printStackTrace() runCatching { terminal.close() }.exceptionOrNull()?.printStackTrace() diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt index fc0b69e7a..1dad2ed5d 100644 --- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt +++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt @@ -25,7 +25,6 @@ package net.mamoe.mirai.console.pure import com.vdurmont.semver4j.Semver import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.withContext import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsole @@ -34,6 +33,7 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.data.MultiFilePluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage +import net.mamoe.mirai.console.internal.plugin.NamedSupervisorJob import net.mamoe.mirai.console.plugin.DeferredPluginLoader import net.mamoe.mirai.console.plugin.PluginLoader import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader @@ -72,7 +72,7 @@ internal class MiraiConsoleImplementationPure override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")), override val configStorageForJarPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")), override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")) -) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(SupervisorJob()) { +) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(NamedSupervisorJob("MiraiConsoleImplementationPure")) { override val mainLogger: MiraiLogger by lazy { MiraiConsole.newLogger("main") }