[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.extension.GlobalComponentStorage
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
import net.mamoe.mirai.console.internal.permission.getPermittedPermissionsAndSource 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.pluginManagerImpl
import net.mamoe.mirai.console.internal.util.runIgnoreException import net.mamoe.mirai.console.internal.util.runIgnoreException
import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.Permission
@ -617,7 +618,13 @@ public object BuiltInCommands {
if (plugin.isEnabled) { if (plugin.isEnabled) {
green().append(plugin.name).reset().append(" v").gold() green().append(plugin.name).reset().append(" v").gold()
} else { } 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() plugin.version.toString()
} }

View File

@ -10,6 +10,7 @@
package net.mamoe.mirai.console.internal.plugin package net.mamoe.mirai.console.internal.plugin
import kotlinx.atomicfu.AtomicLong import kotlinx.atomicfu.AtomicLong
import kotlinx.atomicfu.atomic
import kotlinx.atomicfu.locks.withLock import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.console.MiraiConsole 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
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer 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.AbstractJvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad 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( internal abstract class JvmPluginInternal(
parentCoroutineContext: CoroutineContext, parentCoroutineContext: CoroutineContext,
) : JvmPlugin, CoroutineScope { ) : 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 { final override val parentPermission: Permission by lazy {
PermissionService.INSTANCE.register( 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() } private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() }
final override fun getResourceAsStream(path: String): InputStream? = final override fun getResourceAsStream(path: String): InputStream? =
@ -88,20 +155,31 @@ internal abstract class JvmPluginInternal(
} }
internal fun internalOnDisable() { internal fun internalOnDisable() {
switchStatusOrFail(
expectFlag = PluginStatus.Flags.ALLOW_SWITCH_TO_DISABLE,
update = PluginStatus.DISABLE_PENDING,
)
firstRun = false firstRun = false
kotlin.runCatching { kotlin.runCatching {
val crtThread = Thread.currentThread() val crtThread = Thread.currentThread()
ShutdownDaemon.pluginDisablingThreads.add(crtThread) ShutdownDaemon.pluginDisablingThreads.add(crtThread)
try { try {
pluginStatus.value = PluginStatus.DISABLE_DISABLING
onDisable() onDisable()
} finally { } finally {
ShutdownDaemon.pluginDisablingThreads.remove(crtThread) ShutdownDaemon.pluginDisablingThreads.remove(crtThread)
} }
}.fold( }.fold(
onSuccess = { onSuccess = {
pluginStatus.value = PluginStatus.DISABLED
cancel(CancellationException("plugin disabled")) cancel(CancellationException("plugin disabled"))
}, },
onFailure = { err -> onFailure = { err ->
pluginStatus.value = PluginStatus.CRASHED_DISABLE_ERROR
cancel(CancellationException("Exception while disabling plugin", err)) cancel(CancellationException("Exception while disabling plugin", err))
// @TestOnly // @TestOnly
@ -112,17 +190,32 @@ internal abstract class JvmPluginInternal(
} }
} }
) )
isEnabled = false
} }
@Throws(Throwable::class) @Throws(Throwable::class)
internal fun internalOnLoad() { internal fun internalOnLoad() {
switchStatusOrFail(PluginStatus.ALLOCATED, PluginStatus.LOAD_PENDING)
try {
pluginStatus.value = PluginStatus.LOAD_LOADING
val componentStorage = PluginComponentStorage(this) val componentStorage = PluginComponentStorage(this)
onLoad(componentStorage) onLoad(componentStorage)
pluginStatus.value = PluginStatus.LOAD_LOAD_DONE
GlobalComponentStorage.mergeWith(componentStorage) GlobalComponentStorage.mergeWith(componentStorage)
} catch (e: Throwable) {
pluginStatus.value = PluginStatus.CRASHED_LOAD_ERROR
cancel(CancellationException("Exception while loading plugin", e))
}
} }
internal fun internalOnEnable(): Boolean { internal fun internalOnEnable(): Boolean {
switchStatusOrFail(PluginStatus.LOAD_LOAD_DONE, PluginStatus.ENABLE_PENDING)
parentPermission parentPermission
if (!firstRun) refreshCoroutineContext() if (!firstRun) refreshCoroutineContext()
@ -133,6 +226,7 @@ internal abstract class JvmPluginInternal(
} }
kotlin.runCatching { kotlin.runCatching {
pluginStatus.value = PluginStatus.ENABLE_ENABLING
onEnable() onEnable()
}.fold( }.fold(
onSuccess = { onSuccess = {
@ -142,10 +236,12 @@ internal abstract class JvmPluginInternal(
logger.error(msg) logger.error(msg)
throw AssertionError(msg) throw AssertionError(msg)
} }
isEnabled = true pluginStatus.value = PluginStatus.ENABLED
return true return true
}, },
onFailure = { err -> onFailure = { err ->
pluginStatus.value = PluginStatus.CRASHED_ENABLE_ERROR
cancel(CancellationException("Exception while enabling plugin", err)) cancel(CancellationException("Exception while enabling plugin", err))
logger.error(err) logger.error(err)
@ -189,6 +285,7 @@ internal abstract class JvmPluginInternal(
// for future use // for future use
@Suppress("PropertyName") @Suppress("PropertyName")
@get:JvmSynthetic
internal val _intrinsicCoroutineContext: CoroutineContext by lazy { internal val _intrinsicCoroutineContext: CoroutineContext by lazy {
this as AbstractJvmPlugin this as AbstractJvmPlugin
CoroutineName("Plugin $dataHolderName") CoroutineName("Plugin $dataHolderName")
@ -211,6 +308,7 @@ internal abstract class JvmPluginInternal(
} }
@JvmField @JvmField
@JvmSynthetic
internal val coroutineContextInitializer = { internal val coroutineContextInitializer = {
CoroutineExceptionHandler { context, throwable -> CoroutineExceptionHandler { context, throwable ->
if (throwable.rootCauseOrSelf !is CancellationException) logger.error( if (throwable.rootCauseOrSelf !is CancellationException) logger.error(
@ -228,7 +326,9 @@ internal abstract class JvmPluginInternal(
job.invokeOnCompletion { e -> job.invokeOnCompletion { e ->
if (e != null) { if (e != null) {
if (e !is CancellationException) logger.error(e) if (e !is CancellationException) logger.error(e)
if (this.isEnabled) safeLoader.disable(this) if (pluginStatus.value == PluginStatus.ENABLED) {
safeLoader.disable(this)
}
} }
} }
} }