fix(phase): payload 相位等待期间维度线程跳过 playerPacketQueue.drain,断 getChunk 死锁链

死锁现场(threaddump):主线程在 MainThreadPhaseCoordinator.run 的 managedBlock 自旋,
等所有维度 isTicking()==false;overworld 唯一在 tick,栈:
  drainChunkTasks:277 -> PlayerPacketQueue.drain -> ServerboundMovePlayerPacket.handle
  -> Entity.move -> BlockGetter.clip(碰撞射线) -> getBlockState -> getChunk(未加载)
  -> ServerChunkCache.getChunk:159 -> mainThreadProcessor.managedBlock park 等区块生成。
两方互等:主线程等维度停;维度等区块 future;future 推进需主线程 poll,而主线程在等维度停。

修复:drainChunkTasks 调 playerPacketQueue.drain() 前检查
MainThreadPhaseCoordinator.blocksWorkerStart(server)——主线程正在相位等待时跳过本轮移动包处理,
让维度尽快收口、主线程相位尽快通过;包不丢(留队列),下一拍主线程不再等时再处理。
本次也回退 ChunkPrefetcher 深度/扇面(实测加深反而下降)。
This commit is contained in:
Xiao-no-love committed 2026-10-13 21:15:14 +08:00
1 parent 1ab215dc72
commit 1702d11d8f
2 files changed
+12 -6

No files matched your search

@@ -48,12 +48,12 @@ public final class ChunkPrefetcher {
/** 预取重算间隔(tick),降低每个玩家的逐 tick 开销。 */
private static final int PREFETCH_INTERVAL_TICKS = 5;
/** 预取深度:在玩家视距之外,再向前预取的区块数。冲刺进入大片新区块时,生成速度是瓶颈,
* 加深到 10 格给生成管线更多提前量(沿已生成边缘几乎无额外开销:hasChunk 命中即跳过)。 */
private static final int PREFETCH_DEPTH = 10;
/** 预取深度:在玩家视距之外,再向前预取的区块数(收敛为 4~6 格,避免过度预取浪费算力)。
* 实测:加深到 10 + 扇面反而下降——生成管线吞吐有限,远处预取票挤占近处生成,故保持 6。 */
private static final int PREFETCH_DEPTH = 6;
/** 预取扇面半宽:除正前方外,额外预取左右各 ±1 列(覆盖拐弯/斜向移动)。 */
private static final int PREFETCH_FAN_HALF = 1;
/** 预取扇面半宽:0 = 仅正前方一条线(实测加宽反而拖慢,保持 0)。 */
private static final int PREFETCH_FAN_HALF = 0;
/** 自定义预取 ticket:FULL 级别 + 有限存活时间。 */
private static final TicketType<Unit> PREFETCH_TICKET =
@@ -274,7 +274,13 @@ public final class LevelTickLoop implements Runnable, java.util.concurrent.Execu
continue; // 有就绪任务,继续排空
}
// 区块任务空档期补排空一次玩家包(移动/交互等非命令包),减轻其在高负载下的延迟。
((IPlayerPacketQueue) this.level).modzuozhi$playerPacketQueue().drain();
// 但主线程正在相位等待(payload 独占等所有维度停)时跳过:移动包 handle 会做实体移动
// 碰撞检测 → getChunk(未加载) → mainThreadProcessor.managedBlock park 等区块生成,
// 而此刻主线程正等本维度停、无人推进生成 → 维度 tick 收口被卡住 → 主线程等 10s+ 死锁。
// 包不丢(留在队列),下一拍(主线程不再等)再处理。
if (!dev.modzuozhi.core.compat.MainThreadPhaseCoordinator.blocksWorkerStart(this.level.getServer())) {
((IPlayerPacketQueue) this.level).modzuozhi$playerPacketQueue().drain();
}
if (overBudget) {
break; // 超预算窗口内不可忙等,避免继续占用 worker
}