fix(env): 启动主线程预热协程运行时,修复多插件共存时登录偶发 NoClassDefFoundError 崩溃#702
Open
zhibeigg wants to merge 2 commits into
Open
fix(env): 启动主线程预热协程运行时,修复多插件共存时登录偶发 NoClassDefFoundError 崩溃#702zhibeigg wants to merge 2 commits into
zhibeigg wants to merge 2 commits into
Conversation
非隔离模式(默认)下,kotlinx-coroutines 被重定位成版本键控的共享包 (kotlin<ver>x.coroutines<ver>)加载进各插件 PluginClassLoader;因包名跨插件相同, Paper 插件类加载器组使其跨插件共享、首加载者定义。Dispatchers / CoroutineExceptionHandler 的首次初始化走 ServiceLoader,对触发线程 / 类加载器上下文敏感——当首次触发落在 AsyncPlayerPreLogin("User Authenticator")这类敌对线程时(很多插件在登录时做异步取数), 初始化会非确定性失败(NoClassDefFoundError)并永久毒化共享类,导致此后所有 TabooLib 插件的协程全部不可用。仅在多个 TabooLib 插件共存时偶发、极难排查。 修复:RuntimeEnv.init()(插件加载阶段、运行于启动主线程)加载完协程后,立即用 Class.forName(name, true, loader) 强制这些类在主线程完成首次初始化。JVM 保证类只初始化 一次,敌对线程此后只复用、不再触发脆弱的首次初始化。预热为尽力而为,任何失败都被吞掉, 零行为回归;仅在声明了协程(KOTLIN_COROUTINES_VERSION != null)的插件生效, 不改变任何 API / 加载语义,无新增依赖。
补充当前协程版本内部异常处理实现类,并仅在实际命中类时输出预热调试信息。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
多个 TabooLib 插件共存时,玩家登录会偶发崩溃,且会拖垮所有 TabooLib 插件的协程:
特征:非确定性、仅多插件共存时出现、登录后每秒刷异常、整个数据层瘫痪。单插件 / 单元测试都复现不出。
根因
非隔离模式(默认)下,
RuntimeEnv把kotlinx-coroutines运行时下载、重定位成版本键控的共享包(kotlin<ver>x.coroutines<ver>)加载进各插件的 PluginClassLoader。由于包名跨插件完全相同,Paper 的插件类加载器组使其跨插件共享、首加载者定义。而
Dispatchers/CoroutineExceptionHandler的首次初始化走ServiceLoader,对首次初始化所在的线程 / 类加载器上下文敏感。很多插件会在AsyncPlayerPreLogin(Bukkit 的 "User Authenticator" 线程)做异步取数,于是该共享协程类的首次初始化可能落在这个敌对线程上 → 非确定性失败(NoClassDefFoundError)→ JVM 永久把该类标记为错误类 → 此后所有 TabooLib 插件的协程全废。是否触发取决于"哪个插件、在哪个线程第一个触碰共享协程"——所以是非确定的、且只在多插件环境暴露(单插件时往往恰好先在安全线程初始化了)。
修复
RuntimeEnv.init()(插件加载阶段,运行于启动主线程)加载完协程后,立即用Class.forName(name, true, loader)强制这些"对线程敏感"的协程类在主线程完成首次初始化:<coroutines>.Dispatchers<coroutines>.CoroutineExceptionHandler<coroutines>.internal.MainDispatcherLoader<coroutines>.CoroutineExceptionHandlerImplKtJVM 保证类只初始化一次,之后任意敌对线程再触碰都只复用,不再触发脆弱的首次初始化。重定位包与原始包名都尝试(兼容隔离 / 非隔离 / 服务端自带协程);全程
try/catch吞掉,绝不影响插件加载。影响面
KOTLIN_COROUTINES_VERSION != null)的插件生效。测试
gradlew publishToMavenLocal全模块构建通过。warmupKotlinCoroutines()在[Server thread](主线程)于加载阶段执行,早于 enable 与任何玩家登录;插件正常启用,无回归。