[console] Fix JvmPlugin disabling multiple times; Introduce JvmPlugin status

This commit is contained in:
Karlatemp 2022-12-18 23:11:55 +08:00
parent 2f759c199a
commit 6b1aa9c80b
No known key found for this signature in database
GPG Key ID: BA173CA2B9956C59
2 changed files with 116 additions and 9 deletions

View File

@ -35,6 +35,7 @@ import net.mamoe.mirai.console.internal.data.builtins.DataScope
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
import net.mamoe.mirai.console.internal.permission.getPermittedPermissionsAndSource
import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal
import net.mamoe.mirai.console.internal.pluginManagerImpl
import net.mamoe.mirai.console.internal.util.runIgnoreException
import net.mamoe.mirai.console.permission.Permission
@ -617,7 +618,13 @@ public object BuiltInCommands {
if (plugin.isEnabled) {
green().append(plugin.name).reset().append(" v").gold()
} else {
red().append(plugin.name).append("(disabled)").reset().append(" v").gold()
red().append(plugin.name)
if (plugin is JvmPluginInternal) {
append("(").append(plugin.currentPluginStatus.name.lowercase()).append(")")
} else {
append("(disabled)")
}
reset().append(" v").gold()
}
plugin.version.toString()
}

View File

@ -10,6 +10,7 @@
package net.mamoe.mirai.console.internal.plugin
import kotlinx.atomicfu.AtomicLong
import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole
@ -25,6 +26,7 @@ import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.plugin.PluginManager
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer
import net.mamoe.mirai.console.plugin.id
import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad
@ -47,6 +49,73 @@ internal val <T> T.job: Job where T : CoroutineScope, T : Plugin get() = this.co
internal abstract class JvmPluginInternal(
parentCoroutineContext: CoroutineContext,
) : JvmPlugin, CoroutineScope {
internal enum class PluginStatus {
ALLOCATED,
CRASHED_LOAD_ERROR(Flags.ALLOW_SWITCH_TO_DISABLE),
CRASHED_ENABLE_ERROR(Flags.ALLOW_SWITCH_TO_DISABLE),
CRASHED_DISABLE_ERROR,
LOAD_PENDING,
LOAD_LOADING,
LOAD_LOAD_DONE,
ENABLE_PENDING,
ENABLE_ENABLING,
ENABLED(Flags.ALLOW_SWITCH_TO_DISABLE),
DISABLE_PENDING,
DISABLE_DISABLING,
DISABLED,
;
private val flags: Int
constructor() : this(0)
constructor(flags: Int) {
this.flags = flags
}
internal object Flags { // compiler bug: [UNINITIALIZED_VARIABLE] Variable 'FLAG_ALLOW_SWITCH_TO_DISABLE' must be initialized
internal const val ALLOW_SWITCH_TO_DISABLE = 1 shl 0
}
fun hasFlag(flag: Int): Boolean = flags.and(flag) != 0
}
private val pluginStatus = atomic(PluginStatus.ALLOCATED)
@get:JvmSynthetic
internal val currentPluginStatus: PluginStatus get() = pluginStatus.value
final override val isEnabled: Boolean
get() = pluginStatus.value === PluginStatus.ENABLED
@JvmSynthetic
internal fun switchStatusOrFail(expectFlag: Int, update: PluginStatus) {
val nowStatus = pluginStatus.value
if (nowStatus.hasFlag(expectFlag)) {
if (pluginStatus.compareAndSet(expect = nowStatus, update = update)) {
return
}
error("Failed to switch plugin '$id' status from $nowStatus to $update, current status = ${pluginStatus.value}")
}
error("Failed to switch plugin '$id' status to $update because current status $nowStatus doesn't contain flag ${Integer.toBinaryString(expectFlag)}")
}
@JvmSynthetic
internal fun switchStatusOrFail(expect: PluginStatus, update: PluginStatus) {
val nowStatus = pluginStatus.value
if (nowStatus === expect) {
if (pluginStatus.compareAndSet(expect = expect, update = update)) {
return
}
error("Failed to switch plugin '$id' status from $expect to $update, current status=${pluginStatus.value}")
}
error("Failed to switch plugin '$id' status from $expect to $update, current status = $nowStatus")
}
final override val parentPermission: Permission by lazy {
PermissionService.INSTANCE.register(
@ -55,8 +124,6 @@ internal abstract class JvmPluginInternal(
)
}
final override var isEnabled: Boolean = false
internal set
private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() }
final override fun getResourceAsStream(path: String): InputStream? =
@ -88,20 +155,31 @@ internal abstract class JvmPluginInternal(
}
internal fun internalOnDisable() {
switchStatusOrFail(
expectFlag = PluginStatus.Flags.ALLOW_SWITCH_TO_DISABLE,
update = PluginStatus.DISABLE_PENDING,
)
firstRun = false
kotlin.runCatching {
val crtThread = Thread.currentThread()
ShutdownDaemon.pluginDisablingThreads.add(crtThread)
try {
pluginStatus.value = PluginStatus.DISABLE_DISABLING
onDisable()
} finally {
ShutdownDaemon.pluginDisablingThreads.remove(crtThread)
}
}.fold(
onSuccess = {
pluginStatus.value = PluginStatus.DISABLED
cancel(CancellationException("plugin disabled"))
},
onFailure = { err ->
pluginStatus.value = PluginStatus.CRASHED_DISABLE_ERROR
cancel(CancellationException("Exception while disabling plugin", err))
// @TestOnly
@ -112,17 +190,32 @@ internal abstract class JvmPluginInternal(
}
}
)
isEnabled = false
}
@Throws(Throwable::class)
internal fun internalOnLoad() {
val componentStorage = PluginComponentStorage(this)
onLoad(componentStorage)
GlobalComponentStorage.mergeWith(componentStorage)
switchStatusOrFail(PluginStatus.ALLOCATED, PluginStatus.LOAD_PENDING)
try {
pluginStatus.value = PluginStatus.LOAD_LOADING
val componentStorage = PluginComponentStorage(this)
onLoad(componentStorage)
pluginStatus.value = PluginStatus.LOAD_LOAD_DONE
GlobalComponentStorage.mergeWith(componentStorage)
} catch (e: Throwable) {
pluginStatus.value = PluginStatus.CRASHED_LOAD_ERROR
cancel(CancellationException("Exception while loading plugin", e))
}
}
internal fun internalOnEnable(): Boolean {
switchStatusOrFail(PluginStatus.LOAD_LOAD_DONE, PluginStatus.ENABLE_PENDING)
parentPermission
if (!firstRun) refreshCoroutineContext()
@ -133,6 +226,7 @@ internal abstract class JvmPluginInternal(
}
kotlin.runCatching {
pluginStatus.value = PluginStatus.ENABLE_ENABLING
onEnable()
}.fold(
onSuccess = {
@ -142,10 +236,12 @@ internal abstract class JvmPluginInternal(
logger.error(msg)
throw AssertionError(msg)
}
isEnabled = true
pluginStatus.value = PluginStatus.ENABLED
return true
},
onFailure = { err ->
pluginStatus.value = PluginStatus.CRASHED_ENABLE_ERROR
cancel(CancellationException("Exception while enabling plugin", err))
logger.error(err)
@ -189,6 +285,7 @@ internal abstract class JvmPluginInternal(
// for future use
@Suppress("PropertyName")
@get:JvmSynthetic
internal val _intrinsicCoroutineContext: CoroutineContext by lazy {
this as AbstractJvmPlugin
CoroutineName("Plugin $dataHolderName")
@ -211,6 +308,7 @@ internal abstract class JvmPluginInternal(
}
@JvmField
@JvmSynthetic
internal val coroutineContextInitializer = {
CoroutineExceptionHandler { context, throwable ->
if (throwable.rootCauseOrSelf !is CancellationException) logger.error(
@ -228,7 +326,9 @@ internal abstract class JvmPluginInternal(
job.invokeOnCompletion { e ->
if (e != null) {
if (e !is CancellationException) logger.error(e)
if (this.isEnabled) safeLoader.disable(this)
if (pluginStatus.value == PluginStatus.ENABLED) {
safeLoader.disable(this)
}
}
}
}