Fix unmanaged coroutine job, add names to all CoroutineScopes

This commit is contained in:
Him188 2020-08-27 21:33:55 +08:00
parent c479856380
commit bbf2ead3f0
11 changed files with 122 additions and 86 deletions

View File

@ -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()

View File

@ -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<EventCancelledException> { sendMessage("mirai-console stopped successfully.") }
},
onFailure = {
@OptIn(ConsoleInternalAPI::class)
MiraiConsole.mainLogger.error(it)
ignoreException<EventCancelledException> {
sendMessage(
it.localizedMessage ?: it.message ?: it.toString()
)
}
}
)
}
}.exceptionOrNull()?.let(MiraiConsole.mainLogger::error)
exitProcess(0)
}
}
@ -158,6 +167,24 @@ public object BuiltInCommands {
}
}
internal inline fun <reified E : Throwable, R> ignoreException(block: () -> R): R? {
try {
return block()
} catch (e: Throwable) {
if (e is E) return null
throw e
}
}
internal inline fun <reified E : Throwable> 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)"

View File

@ -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
}

View File

@ -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)
})

View File

@ -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> 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

View File

@ -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()

View File

@ -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 ?: "<unnamed>", 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<out PluginData> = arrayOf()
private val configs: Array<out PluginConfig> = 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"
}

View File

@ -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<Void?> {
return this.future {

View File

@ -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
}

View File

@ -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()

View File

@ -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")
}