|
@@ -0,0 +1,105 @@
|
|
|
|
+# Mirai Console Backend - JVM Plugins - Debug
|
|
|
|
+
|
|
|
|
+> 建议在 Java 9+ 的环境中进行排错, mirai-console 在 java 9+ 中的错误堆栈中报告了错误类来自哪个类加载器
|
|
|
|
+
|
|
|
|
+## 错误堆栈基本解析
|
|
|
|
+
|
|
|
|
+```log
|
|
|
|
+java.lang.Exception: Thread stack dump
|
|
|
|
+ at java.base/java.lang.Thread.dumpStack(Thread.java)
|
|
|
|
+ at example-plugin.mirai2.jar[shared]//com.example.exmapleplugin.sharedlib.SharedLib.handle(shared.kt:6)
|
|
|
|
+ at example-plugin.mirai2.jar[private]//com.example.exmapleplugin.privatelib.PrivLib.cmd(priv.kt:5)
|
|
|
|
+ at example-plugin.mirai2.jar//com.example.exmapleplugin.MyCommand.cmd(MyCommand.kt:63)
|
|
|
|
+ at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
|
|
|
|
+ at ......
|
|
|
|
+ at net.mamoe.mirai.console.command.CommandManager.executeCommand$default(CommandManager.kt:125)
|
|
|
|
+ at chat-command-0.5.1.jar//net.mamoe.mirai.console.plugins.chat.command.PluginMain.handleCommand(PluginMain.kt:86)
|
|
|
|
+ at chat-command-0.5.1.jar//net.mamoe.mirai.console.plugins.chat.command.PluginMain$onEnable$2$1.invokeSuspend(PluginMain.kt:69)
|
|
|
|
+ at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
|
|
|
|
+ at ......
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+来自 plugin 本身的类加载器的堆栈会以 插件文件名 开头, 其中 `...[private]` `....[shared]` 都是该插件使用的类库.
|
|
|
|
+
|
|
|
|
+- `[shared]` 代表是共享库, 其中的类可以被依赖此插件的其他插件解析到
|
|
|
|
+- `[private]` 代表是私有库, 仅该插件自己内部使用, 依赖此插件的其他插件将不能解析到此类加载器的类
|
|
|
|
+
|
|
|
|
+## 多插件间数据交换结果和预期不符合
|
|
|
|
+
|
|
|
|
+多插件间数据结果不一致 90% 是因为缺少依赖关系导致的未解析到相同的类导致结果不一致
|
|
|
|
+
|
|
|
|
+关于如何建立关系, 见 [JVMPlugin - Data Exchange](./JVMPlugin-DataExchange.md)
|
|
|
|
+
|
|
|
|
+可以使用以下代码确定是否是因为类链接错误导致的数据不一致
|
|
|
|
+
|
|
|
|
+```kotlin
|
|
|
|
+fun MiraiLogger.dumpClass(klass: Class<*>) {
|
|
|
|
+ info { "Class name: $klass" }
|
|
|
|
+ info { " |- loader: ${klass.classLoader}" }
|
|
|
|
+ info { " |- module: ${klass.module}" }
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+```java
|
|
|
|
+public static void dumpClass(MiraiLogger logger, Class<?> klass) {
|
|
|
|
+ logger.info("Class name: " + klass);
|
|
|
|
+ logger.info(" |- loader: " + klass.getClassLoader());
|
|
|
|
+ logger.info(" |- module: " + klass.getModule());
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+## 使用的第三方库报错没有模块实现
|
|
|
|
+
|
|
|
|
+在插件初始化的时候, 线程上下文类加载器依然还是 console 的系统类加载器 (`AppClassLoader`), 需要手动将其切换到插件的类加载器
|
|
|
|
+
|
|
|
|
+详见 [Issue Comment](https://github.com/mamoe/mirai/issues/2138#issuecomment-1179673302)
|
|
|
|
+
|
|
|
|
+```kotlin
|
|
|
|
+fun onEnable() {
|
|
|
|
+ val oThreadCtxLoader = Thread.currentThread().contextClassLoader
|
|
|
|
+ try {
|
|
|
|
+ Thread.currentThread().contextClassLoader = javaClass.classLoader
|
|
|
|
+ // .......
|
|
|
|
+ } finally {
|
|
|
|
+ Thread.currentThread().contextClassLoader = oThreadCtxLoader
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+## java.lang.LinkageError: loader constraint violation
|
|
|
|
+
|
|
|
|
+```log
|
|
|
|
+java.lang.LinkageError: loader constraint violation: when resolving method 'void io.ktor.client.request.HttpRequestBuilder.setMethod(io.ktor.http.HttpMethod)' the class loader 'test-ktor-dev.mirai2.jar' @61dde151 of the current class, com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2, and the class loader 'app' for the method's defining class, io/ktor/client/request/HttpRequestBuilder, have different Class objects for the type io/ktor/http/HttpMethod used in the signature (com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2 is in unnamed module of loader 'test-ktor-dev.mirai2.jar' @61dde151, parent loader 'global-shared' @32b9bd12; io.ktor.client.request.HttpRequestBuilder is in unnamed module of loader 'app')
|
|
|
|
+ at .................
|
|
|
|
+
|
|
|
|
+java.lang.LinkageError: loader constraint violation:
|
|
|
|
+ when resolving method 'void io.ktor.client.request.HttpRequestBuilder.setMethod(io.ktor.http.HttpMethod)'
|
|
|
|
+ the class loader 'test-ktor-dev.mirai2.jar' @61dde151 of the current class, com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2,
|
|
|
|
+ and
|
|
|
|
+ the class loader 'app' for the method's defining class, io/ktor/client/request/HttpRequestBuilder,
|
|
|
|
+
|
|
|
|
+ have different Class objects for the type io/ktor/http/HttpMethod used in the signature
|
|
|
|
+
|
|
|
|
+ (
|
|
|
|
+ com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2 is in unnamed module of loader 'test-ktor-dev.mirai2.jar' @61dde151,
|
|
|
|
+ parent loader 'global-shared' @32b9bd12;
|
|
|
|
+
|
|
|
|
+ io.ktor.client.request.HttpRequestBuilder is in unnamed module of loader 'app'
|
|
|
|
+ )
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+翻译
|
|
|
|
+
|
|
|
|
+```log
|
|
|
|
+JVM 无法解析 com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2 中的方法引用
|
|
|
|
+ 'void io.ktor.client.request.HttpRequestBuilder.setMethod(io.ktor.http.HttpMethod)'
|
|
|
|
+
|
|
|
|
+搜索到的 'io.ktor.client.request.HttpRequestBuilder' 位于系统类加载器 'app' 中
|
|
|
|
+
|
|
|
|
+HttpRequestBuilder 中的 HttpMethod 引用和
|
|
|
|
+ TestKtor$getTailrec$$inlined$get$2 得到的 HttpMethod 引用
|
|
|
|
+不一致, 无法连接
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+结论: 插件没有附带完整的 ktor 依赖导致部分类解析至插件库加载器, 部分类解析至系统类加载器
|