diag(threadownership): 慢相位等待超2s时 dump 主线程+DR线程栈,抓死锁现场

背景:18:44 测试出现 tick 死锁——主线程在 custom payloads 相位的 managedBlock 里等
overworld 维度 loop 停(isTicking),而 overworld 卡在 connections 阶段的
SableMainThreadBridge.runForLevel(server.execute排队 + completion.get 等主线程),
tick 永不返回,单 tick 从 40s 单调涨到 203s。

ModernFix watchdog 的 Thread Dump 不含 Server thread/DR 线程,现场缺失。
本改动:ThreadOwnershipDiagnostics.slowPhase 等待 >2s 时 dumpKeyThreads(),
用 ThreadMXBean 输出 Server thread + 所有 dimensionalripper_* 线程的完整栈,
含 lockName/lockOwner/lockInfo/blocked/waited/ownedSynchronizers。
时间窗口 30s 限流,避免刷屏。纯诊断,不改执行逻辑。
This commit is contained in:
Xiao-no-love committed 2026-10-13 19:44:20 +08:00
1 parent 74cc5505b0
commit 01a966fffc
1 file changed
+36
@@ -87,6 +87,42 @@ public final class ThreadOwnershipDiagnostics {
long next = NEXT_SLOW_WARNING.get();
if ((next == 0 || now - next >= 0) && NEXT_SLOW_WARNING.compareAndSet(next, now + TimeUnit.SECONDS.toNanos(30)))
ModZuozhi.LOGGER.warn("[ThreadOwnership] {} waiting {} ms; {}", reason, (now-began)/1_000_000, report(server));
// 等待超过 2 秒:dump 主线程 + 所有 DR 线程栈,用于诊断死锁现场(每次慢相位最多一次)。
if (now - began >= TimeUnit.SECONDS.toNanos(2)) {
long nextDump = NEXT_DUMP.get();
if ((nextDump == 0 || now - nextDump >= 0)
&& NEXT_DUMP.compareAndSet(nextDump, now + TimeUnit.SECONDS.toNanos(30))) {
ModZuozhi.LOGGER.error("[ThreadOwnership] {} 等待超过 2s,dump 关键线程栈(诊断死锁)", reason);
dumpKeyThreads();
}
}
}
private static final AtomicLong NEXT_DUMP = new AtomicLong();
/** Dump Server thread + 所有 dimensionalripper_* 线程的完整栈(含锁信息),仅诊断用。 */
public static void dumpKeyThreads() {
try {
java.lang.management.ThreadMXBean mx = java.lang.management.ManagementFactory.getThreadMXBean();
for (java.lang.management.ThreadInfo info : mx.dumpAllThreads(true, true)) {
if (info == null) continue;
String name = info.getThreadName();
boolean key = name.equals("Server thread") || name.startsWith("dimensionalripper_");
if (!key) continue;
StringBuilder sb = new StringBuilder().append(name)
.append(" #").append(info.getThreadId()).append(' ').append(info.getThreadState())
.append(" lockName=").append(info.getLockName())
.append(" lockOwner=").append(info.getLockOwnerName())
.append(" lockInfo=").append(String.valueOf(info.getLockInfo()))
.append(" blocked=").append(info.getBlockedCount())
.append(" waited=").append(info.getWaitedCount());
for (StackTraceElement frame : info.getStackTrace()) sb.append("\n\tat ").append(frame);
for (var lock : info.getLockedSynchronizers()) sb.append("\n\towned: ").append(lock);
ModZuozhi.LOGGER.error("[ThreadOwnership dump] {}", sb);
}
} catch (RuntimeException failure) {
ModZuozhi.LOGGER.error("[ThreadOwnership] 线程栈 dump 失败", failure);
}
}
public static void phaseTiming(String reason, long began, long drained) {