feat(compat): expose shared DistanceManager lock for cross-mod ticket iteration

将 DistanceManagerMixin 的私有 static LOCK 抽到公开的
dev.modzuozhi.core.compat.DistanceManagerLock.LOCK,供 ServerWarashi
的 TicketScanService 通过反射共用,实现 ticket 表「读-写互斥」。

背景:DR 维度线程在 tick 中并发增删 DistanceManager.tickets
(Long2ObjectOpenHashMap),warashi 主线程遍历同表做采样归属快照时,
fastutil fail-fast 迭代器内部 wrapped 被置 null -> nextEntry NPE 崩服。
DR 原有锁保护所有写路径,但读路径(warashi 遍历)不在锁内。

- 新增 DistanceManagerLock(公开 ReentrantLock)
- DistanceManagerMixin 的 LOCK 指向该共享锁
This commit is contained in:
Xiao-no-love committed 2026-10-13 17:02:08 +08:00
1 parent 72936288d9
commit d76d6593bd
2 files changed
+24 -3

No files matched your search

@@ -0,0 +1,22 @@
package dev.modzuozhi.core.compat;
import java.util.concurrent.locks.ReentrantLock;
/**
* DistanceManager ticket 表的共享锁。
* <p>
* DR 让维度 tick 跑在独立线程后,多个线程会并发修改 {@code DistanceManager.tickets}
* ({@code Long2ObjectOpenHashMap},非线程安全)。{@link dev.modzuozhi.mixin.DistanceManagerMixin}
* 用它串行化所有 ticket 写路径。
* <p>
* <b>跨 mod 可见性</b>:ServerWarashi 的 {@code TicketScanService} 在主线程遍历同一张 ticket 表
* (性能采样归属快照),若不持同一把锁,遍历会与维度线程的写并发 → fastutil fail-fast 迭代器
* NPE。因此本锁<b>公开</b>,warashi 通过反射获取并共用,实现「读-写互斥」。
*/
public final class DistanceManagerLock {
/** 所有 DistanceManager ticket 读/写路径共用的锁。 */
public static final ReentrantLock LOCK = new ReentrantLock();
private DistanceManagerLock() {
}
}
@@ -10,8 +10,7 @@ import net.minecraft.server.level.Ticket;
import net.minecraft.world.level.ChunkPos;
import org.spongepowered.asm.mixin.Mixin;
import java.util.concurrent.locks.ReentrantLock;
import dev.modzuozhi.core.compat.DistanceManagerLock;
/**
* {@code DistanceManager}(区块 ticket 调度)并发保护。
* <p>
@@ -26,7 +25,7 @@ import java.util.concurrent.locks.ReentrantLock;
*/
@Mixin(DistanceManager.class)
public class DistanceManagerMixin {
private static final ReentrantLock LOCK = new ReentrantLock();
private static final java.util.concurrent.locks.ReentrantLock LOCK = DistanceManagerLock.LOCK;
@WrapMethod(method = "addTicket")
private void modzuozhi_lockAddTicket(long chunkPos, Ticket<?> ticket, Operation<Void> original) {