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:
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) {
|
||||
|
||||
Reference in new issue
Block a user