Structured init phases; Load plugins and init services in order

This commit is contained in:
Him188 2020-09-08 20:20:53 +08:00
parent 102f359a11
commit e1f8125163
6 changed files with 164 additions and 79 deletions

View File

@ -46,6 +46,8 @@ import java.nio.file.Path
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
/**
@ -84,59 +86,109 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
override fun createLogger(identity: String?): MiraiLogger = instance.createLogger(identity)
@OptIn(ConsoleExperimentalAPI::class, ExperimentalPermission::class)
@Suppress("RemoveRedundantBackticks")
internal fun doStart() {
val buildDateFormatted =
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
mainLogger.info { "Starting mirai-console..." }
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
mainLogger.info { frontEndDescription.render() }
phase `greeting`@{
val buildDateFormatted =
buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
if (coroutineContext[Job] == null) {
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
}
if (coroutineContext[CoroutineExceptionHandler] == null) {
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a CoroutineExceptionHandler in it.")
mainLogger.info { "Starting mirai-console..." }
mainLogger.info { "Backend: version $version, built on $buildDateFormatted." }
mainLogger.info { frontEndDescription.render() }
}
MiraiConsole.job.invokeOnCompletion {
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
}
phase `check coroutineContext`@{
if (coroutineContext[Job] == null) {
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.")
}
if (coroutineContext[CoroutineExceptionHandler] == null) {
throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a CoroutineExceptionHandler in it.")
}
mainLogger.verbose { "Loading configurations..." }
ConsoleDataScope.reloadAll()
mainLogger.verbose { "Loading PermissionService..." }
PermissionService.INSTANCE.let { ps ->
if (ps is StorablePermissionService<*>) {
ConsoleDataScope.addAndReloadConfig(ps.config)
mainLogger.verbose { "Reloaded PermissionService settings." }
MiraiConsole.job.invokeOnCompletion {
Bot.botInstances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) }
}
}
mainLogger.verbose { "Loading built-in commands..." }
BuiltInCommands.registerAll()
mainLogger.verbose { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
CommandManager
CommandManagerImpl.commandListener // start
// start
mainLogger.verbose { "Loading plugins..." }
PluginManager
PluginManagerImpl.loadEnablePlugins()
mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." }
mainLogger.info { "mirai-console started successfully." }
phase `load configurations`@{
mainLogger.verbose { "Loading configurations..." }
ConsoleDataScope.reloadAll()
}
runBlocking {
for ((id, password) in AutoLoginConfig.plainPasswords) {
mainLogger.info { "Auto-login $id" }
MiraiConsole.addBot(id, password).alsoLogin()
val pluginLoadSession: PluginManagerImpl.PluginLoadSession
phase `load plugins`@{
PluginManager // init
mainLogger.verbose { "Loading PluginLoader provider plugins..." }
PluginManagerImpl.loadEnablePluginProviderPlugins()
mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." }
mainLogger.verbose { "Scanning high-priority extension and normal plugins..." }
pluginLoadSession = PluginManagerImpl.scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider()
mainLogger.verbose { "${pluginLoadSession.allKindsOfPlugins.size} plugin(s) found." }
mainLogger.verbose { "Loading Extension provider plugins..." }
PluginManagerImpl.loadEnableHighPriorityExtensionPlugins(pluginLoadSession)
mainLogger.verbose { "${PluginManager.plugins.size} such plugin(s) loaded." }
}
phase `load PermissionService`@{
mainLogger.verbose { "Loading PermissionService..." }
PermissionService.INSTANCE.let { ps ->
if (ps is StorablePermissionService<*>) {
ConsoleDataScope.addAndReloadConfig(ps.config)
mainLogger.verbose { "Reloaded PermissionService settings." }
}
}
}
for ((id, password) in AutoLoginConfig.md5Passwords) {
mainLogger.info { "Auto-login $id" }
MiraiConsole.addBot(id, password).alsoLogin()
phase `prepare commands`@{
mainLogger.verbose { "Loading built-in commands..." }
BuiltInCommands.registerAll()
mainLogger.verbose { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" }
CommandManager
CommandManagerImpl.commandListener // start
}
phase `load normal plugins`@{
mainLogger.verbose { "Loading normal plugins..." }
val count = PluginManagerImpl.loadEnableNormalPlugins(pluginLoadSession)
mainLogger.verbose { "$count normal plugin(s) loaded." }
}
mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) loaded." }
phase `auto-login bots`@{
runBlocking {
for ((id, password) in AutoLoginConfig.plainPasswords) {
mainLogger.info { "Auto-login $id" }
MiraiConsole.addBot(id, password).alsoLogin()
}
for ((id, password) in AutoLoginConfig.md5Passwords) {
mainLogger.info { "Auto-login $id" }
MiraiConsole.addBot(id, password).alsoLogin()
}
}
}
PostStartupExtension.useExtensions { it() }
mainLogger.info { "mirai-console started successfully." }
}
@Retention(AnnotationRetention.SOURCE)
@DslMarker
private annotation class MiraiIsCool
@MiraiIsCool
private inline fun phase(block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
}

View File

@ -22,7 +22,6 @@ import net.mamoe.mirai.console.plugin.PluginLoadException
import net.mamoe.mirai.console.plugin.jvm.*
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.info
import java.io.File
import java.net.URLClassLoader
import java.util.concurrent.ConcurrentHashMap
@ -73,8 +72,8 @@ internal object JarPluginLoaderImpl :
URLClassLoader(arrayOf(it.toURI().toURL()), MiraiConsole::class.java.classLoader)
}.onEach { (_, classLoader) ->
classLoaders.add(classLoader)
}.asSequence().findAllInstances().onEach { loaded ->
logger.info { "Successfully initialized JvmPlugin ${loaded}." }
}.asSequence().findAllInstances().onEach {
//logger.verbose { "Successfully initialized JvmPlugin ${loaded}." }
}.onEach { (file, plugin) ->
pluginFileToInstanceMap[file] = plugin
} + pluginFileToInstanceMap.asSequence()
@ -97,9 +96,13 @@ internal object JarPluginLoaderImpl :
override fun enable(plugin: JvmPlugin) {
if (plugin.isEnabled) return
ensureActive()
if (plugin is JvmPluginInternal) {
plugin.internalOnEnable()
} else plugin.onEnable()
runCatching {
if (plugin is JvmPluginInternal) {
plugin.internalOnEnable()
} else plugin.onEnable()
}.getOrElse {
throw PluginLoadException("Exception while loading ${plugin.description.name}", it)
}
}
override fun disable(plugin: JvmPlugin) {

View File

@ -113,48 +113,58 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
)
}
/**
* STEPS:
* 1. 遍历插件列表, 使用 [builtInLoaders] 加载 [PluginKind.LOADER] 类型的插件
* 2. [启动][PluginLoader.enable] 所有 [PluginKind.LOADER] 的插件
* 3. 使用内建和所有插件提供的 [PluginLoader] 加载全部除 [PluginKind.LOADER] 外的插件列表.
* 4. 解决依赖并排序
* 5. 依次 [PluginLoader.load]
* 但不 [PluginLoader.enable]
*
* @return [builtInLoaders] 可以加载的插件. 已经完成了 [PluginLoader.load], 但没有 [PluginLoader.enable]
*/
@Suppress("UNCHECKED_CAST")
@Throws(PluginMissingDependencyException::class)
internal fun loadEnablePlugins() {
loadAndEnableLoaderProviders()
loadPluginLoaderProvidedByPlugins()
internal class PluginLoadSession(
val allKindsOfPlugins: List<Pair<PluginLoader<*, *>, List<PluginDescriptionWithLoader>>>
)
// Phase #2
internal fun scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider(): PluginLoadSession {
return PluginLoadSession(loadersLock.withLock { _pluginLoaders.listAllPlugins() })
}
// Phase #0
internal fun loadEnablePluginProviderPlugins() {
loadAndEnableLoaderProvidersUsingBuiltInLoaders()
}
// Phase #3
internal fun loadEnableHighPriorityExtensionPlugins(session: PluginLoadSession): Int {
loadersLock.withLock {
_pluginLoaders.listAllPlugins().flatMap { it.second }
.also {
logger.debug("All plugins: ${it.joinToString { (_, desc, _) -> desc.name }}")
}
session.allKindsOfPlugins.flatMap { it.second }
.filter { it.kind == PluginKind.HIGH_PRIORITY_EXTENSIONS }
.sortByDependencies()
.also {
logger.debug("Sorted plugins: ${it.joinToString { (_, desc, _) -> desc.name }}")
}
.loadAndEnableAllInOrder()
.also { it.loadAndEnableAllInOrder() }
.let { return it.size }
}
}
private fun loadPluginLoaderProvidedByPlugins() {
// Phase #4
internal fun loadEnableNormalPlugins(session: PluginLoadSession): Int {
loadersLock.withLock {
PluginLoaderProvider.useExtensions {
logger.info { "Loaded PluginLoader ${it.instance} from $" }
_pluginLoaders.add(it.instance)
session.allKindsOfPlugins.flatMap { it.second }
.filter { it.kind == PluginKind.NORMAL }
.sortByDependencies()
.also { it.loadAndEnableAllInOrder() }
.let { return it.size }
}
}
// Phase #1
internal fun loadPluginLoaderProvidedByPlugins() {
loadersLock.withLock {
PluginLoaderProvider.useExtensions { ext, plugin ->
logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin.name}" }
_pluginLoaders.add(ext.instance)
}
}
}
private fun List<PluginDescriptionWithLoader>.loadAndEnableAllInOrder() {
return this.forEach { (loader, _, plugin) ->
this.forEach { (loader, _, plugin) ->
loader.loadPluginNoEnable(plugin)
}
this.forEach { (loader, _, plugin) ->
loader.enablePlugin(plugin)
}
}
@ -171,7 +181,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
*/
@Suppress("UNCHECKED_CAST")
@Throws(PluginMissingDependencyException::class)
private fun loadAndEnableLoaderProviders(): List<PluginDescriptionWithLoader> {
private fun loadAndEnableLoaderProvidersUsingBuiltInLoaders(): List<PluginDescriptionWithLoader> {
val allDescriptions =
builtInLoaders.listAllPlugins()
.asSequence()

View File

@ -79,7 +79,10 @@ internal object BuiltInPermissionService : AbstractConcurrentPermissionService<P
override val permissionType: KClass<PermissionImpl>
get() = PermissionImpl::class
override val permissions: MutableMap<PermissionId, PermissionImpl> get() = config.permissions
override val grantedPermissionsMap: MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>> get() = config.grantedPermissionMap
@Suppress("UNCHECKED_CAST")
override val grantedPermissionsMap: MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
get() = config.grantedPermissionMap as MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
override fun createPermission(id: PermissionId, description: String, base: PermissionId?): PermissionImpl =
PermissionImpl(id, description, base)

View File

@ -36,8 +36,8 @@ public interface StorablePermissionService<P : Permission> : PermissionService<P
ConcurrentHashMap()
)
public val grantedPermissionMap: MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>
by value<MutableMap<PermissionId, MutableCollection<PermissibleIdentifier>>>(ConcurrentHashMap())
public val grantedPermissionMap: MutableMap<PermissionId, List<PermissibleIdentifier>>
by value<MutableMap<PermissionId, List<PermissibleIdentifier>>>(ConcurrentHashMap())
.withDefault { CopyOnWriteArrayList() }
public companion object {

View File

@ -12,17 +12,34 @@ package net.mamoe.mirai.console.plugin.description
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.serializer
import net.mamoe.mirai.console.extension.Extension
import net.mamoe.mirai.console.extensions.BotConfigurationAlterer
import net.mamoe.mirai.console.extensions.PermissionServiceProvider
import net.mamoe.mirai.console.internal.data.map
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.description.PluginKind.*
/**
* 插件类型
* 插件类型.
*
* 插件类型将影响加载顺序: [LOADER] -> [HIGH_PRIORITY_EXTENSIONS] -> [NORMAL].
*
* 依赖解决过程与插件类型有很大关联. 在一个较早的阶段, 只会解决在此阶段加载的插件. 意味着 [LOADER] 不允许依赖一个 [NORMAL] 类型的插件.
*/
@Serializable(with = PluginKind.AsStringSerializer::class)
public enum class PluginKind {
/** 表示此插件提供一个 [PluginLoader], 应在加载其他 [NORMAL] 类型插件前加载 */
/** 表示此插件提供一个 [PluginLoader], 也可以同时提供其他 [Extension] 应最早被加载 */
LOADER,
/**
* 表示此插件提供一些高优先级的 [Extension], 应在加载其他 [NORMAL] 类型插件前加载
*
* 高优先级的 [Extension] 通常是覆盖 Console 内置的部分服务的扩展. [PermissionServiceProvider].
*
* 一些普通的 [Extension], [BotConfigurationAlterer], 也可以使用 [NORMAL] 类型插件注册.
*/
HIGH_PRIORITY_EXTENSIONS,
/** 表示此插件为一个通常的插件, 按照正常的依赖关系加载. */
NORMAL;