【崩溃】扫描未加载维度时 HTTP 线程热加载维度,与服务器线程竞争导致崩溃(NPE)
环境
- GTNH: 2.9.0-beta-1(mod 为 beta-2 构建,如与本问题相关请告知)
- webapi: 2.9.0-beta2-0.7-pre
- Minecraft 1.7.10 / Forge 10.13.4.1614 / Java 25.0.2(Oracle)
- 服务端专用部署(无客户端)
崩溃现象
服务器运行中(已完全启动),通过 Web 界面提交扫描请求后服务器崩溃:
java.lang.NullPointerException: Cannot invoke "it.unimi.dsi.fastutil.longs.LongArrayList.getLong(int)" because "this.wrapped" is null
at it.unimi.dsi.fastutil.longs.LongOpenHashSet$SetIterator.nextLong(LongOpenHashSet.java:551)
at net.minecraft.world.gen.ChunkProviderServer.func_73156_b(ChunkProviderServer.java:2589)
at net.minecraft.world.WorldServer.func_72835_b(WorldServer.java:165)
at net.minecraft.server.MinecraftServer.func_71190_q(MinecraftServer.java:625)
crash report 描述为 Exception ticking world,崩溃线程为 Server thread。
日志证据(崩溃前最后一条请求)
[ERROR] [webapi]: [POST] /gt5/scan?chunkX=-4&chunkZ=-3&dim=180 - Error after 0.122ms
java.lang.RuntimeException: Cannot Hotload Dim: Overworld is not Loaded!
at net.minecraftforge.common.DimensionManager.initDimension(DimensionManager.java:230)
at net.minecraft.server.MinecraftServer.func_71218_a(MinecraftServer.java:705)
at love.shirokasoke.webapi.webserver.handlers.gt5.GT5ChunkScanHandler.handleSubmit(GT5ChunkScanHandler.java:115)
注意这条请求运行在 HTTP 线程池(pool-9-thread-5),而非 Minecraft Server thread。
复现步骤
- 服务端完全启动后,先提交一个已加载维度(如主世界 dim=0)的扫描任务(POST
/gt5/scan),256 个子任务异步进行中
- 扫描未完成时,提交一个当前未加载维度(如 GTNH Personal World dim=180)的扫描请求
- 服务器崩溃
原因分析
GT5ChunkScanHandler.handleSubmit 中:
MinecraftServer server = getServer();
WorldServer world = server.worldServerForDimension(dim); // 在 HTTP 线程执行
这段代码运行在 HTTP 线程上。当 dim 对应的维度未加载时,Forge/GTNH 的 worldServerForDimension 会触发 DimensionManager.initDimension 进行维度热加载,即在非 Server thread 上修改 Minecraft 世界状态(维度表、chunk provider 的 chunk 集合等)。
与此同时 Server thread 正在正常 tick 世界(并且之前提交的扫描任务的 256 个子任务也在 Server thread 队列中执行),两个线程并发操作同一组集合(fastutil LongOpenHashSet),导致 Server thread 在 ChunkProviderServer 迭代 chunk 集合时命中 fastutil 迭代器的 wrapped == null NPE——这是典型的跨线程并发修改特征。
顺带一提:日志中的 Cannot Hotload Dim: Overworld is not Loaded! 并非主世界真的未加载,而是 HTTP 线程跨线程读取世界引用时的可见性问题(读到过期/未同步的引用),同一现象的另一面。
建议修复方向
将维度获取/热加载逻辑放进服务器线程执行,例如把 handleSubmit 中的维度解析与 worldServerForDimension 调用包进 ServerThreadDispatcher.scheduleOnServerThread,或在提交前先在校验阶段用 scheduleOnServerThread 确认维度已加载(未加载则直接返回 4xx,而不是隐式热加载)。扫描任务本身已经是异步投递到 Server thread 的,只是提交阶段的维度解析漏了。
附
完整 crash report 与日志可提供,如有需要请告知。
【崩溃】扫描未加载维度时 HTTP 线程热加载维度,与服务器线程竞争导致崩溃(NPE)
环境
崩溃现象
服务器运行中(已完全启动),通过 Web 界面提交扫描请求后服务器崩溃:
crash report 描述为
Exception ticking world,崩溃线程为 Server thread。日志证据(崩溃前最后一条请求)
注意这条请求运行在 HTTP 线程池(
pool-9-thread-5),而非 Minecraft Server thread。复现步骤
/gt5/scan),256 个子任务异步进行中原因分析
GT5ChunkScanHandler.handleSubmit中:这段代码运行在 HTTP 线程上。当
dim对应的维度未加载时,Forge/GTNH 的worldServerForDimension会触发DimensionManager.initDimension进行维度热加载,即在非 Server thread 上修改 Minecraft 世界状态(维度表、chunk provider 的 chunk 集合等)。与此同时 Server thread 正在正常 tick 世界(并且之前提交的扫描任务的 256 个子任务也在 Server thread 队列中执行),两个线程并发操作同一组集合(fastutil
LongOpenHashSet),导致 Server thread 在ChunkProviderServer迭代 chunk 集合时命中 fastutil 迭代器的wrapped == nullNPE——这是典型的跨线程并发修改特征。顺带一提:日志中的
Cannot Hotload Dim: Overworld is not Loaded!并非主世界真的未加载,而是 HTTP 线程跨线程读取世界引用时的可见性问题(读到过期/未同步的引用),同一现象的另一面。建议修复方向
将维度获取/热加载逻辑放进服务器线程执行,例如把
handleSubmit中的维度解析与worldServerForDimension调用包进ServerThreadDispatcher.scheduleOnServerThread,或在提交前先在校验阶段用scheduleOnServerThread确认维度已加载(未加载则直接返回 4xx,而不是隐式热加载)。扫描任务本身已经是异步投递到 Server thread 的,只是提交阶段的维度解析漏了。附
完整 crash report 与日志可提供,如有需要请告知。