blob: f675efc252fb751fdeac97ee2cfa85c344b55f8f [file] [log] [blame]
package org.jdesktop.swinghelper.debug;
import static java.lang.Math.min;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
final class DispatchInfo {
// Provides the index to each hang event.
private static final AtomicInteger hangCount = new AtomicInteger(0);
// The last-dumped hung stack trace for this dispatch.
private StackTraceElement[] lastReportedStack;
// If so; what was the identifying hang number?
private int hangNumber;
// The EDT for this dispatch (for the purpose of getting stack traces).
final Thread eventDispatchThread = Thread.currentThread();
// The last time in milliseconds at which we saw a dispatch on the above thread.
long lastDispatchTimeMillis = System.currentTimeMillis();
private final Consumer<String> logConsumer;
DispatchInfo(Consumer<String> logConsumer) {
this.logConsumer = logConsumer;
}
void checkForHang() {
if (timeSoFar() > EventDispatchThreadHangMonitor.UNREASONABLE_DISPATCH_DURATION_MS) {
examineHang();
}
}
private void examineHang() {
StackTraceElement[] currentStack = eventDispatchThread.getStackTrace();
if (isWaitingForNextEvent(currentStack)) {
// Don't be fooled by a modal dialog if it's waiting for its next event.
// As long as the modal dialog's event pump doesn't get stuck, it's okay for the outer pump to
// be suspended.
return;
}
if (stacksEqual(lastReportedStack, currentStack)) {
// Skips reporting the same hang every time the timer goes off.
return;
}
lastReportedStack = currentStack;
hangNumber = hangCount.addAndGet(/* delta= */ 1);
logConsumer.accept(
"(hang #"
+ hangNumber
+ ") event dispatch thread stuck processing event for "
+ timeSoFar()
+ " ms:"
+ stackTraceToString(currentStack));
checkForDeadlock();
}
private long timeSoFar() {
return System.currentTimeMillis() - lastDispatchTimeMillis;
}
public void dispose() {
if (lastReportedStack != null) {
logConsumer.accept(
"(hang #" + hangNumber + ") event dispatch thread unstuck after " + timeSoFar() + " ms.");
}
}
// Checks whether the given stack looks like it's waiting for another event.
// This relies on JDK implementation details.
private static boolean isWaitingForNextEvent(StackTraceElement[] currentStack) {
for (int i = 0; i < min(5, currentStack.length); i++) {
if (stackTraceElementIs(currentStack[i], "java.awt.EventQueue", "getNextEvent", false)) {
return true;
}
}
return false;
}
// We can't use StackTraceElement.equals because that insists on checking the filename and line
// number.
// That would be version-specific.
private static boolean stackTraceElementIs(
StackTraceElement e, String className, String methodName, boolean isNative) {
return e.getClassName().equals(className)
&& e.getMethodName().equals(methodName)
&& e.isNativeMethod() == isNative;
}
private String stackTraceToString(StackTraceElement[] stackTrace) {
StringBuilder result = new StringBuilder();
// We used to avoid showing any code above where this class gets
// involved in event dispatch, but that hides potentially useful
// information when dealing with modal dialogs. Maybe we should
// reinstate that, but search from the other end of the stack?
for (StackTraceElement stackTraceElement : stackTrace) {
String indentation = " ";
result.append("\n").append(indentation).append(stackTraceElement);
}
return result.toString();
}
private boolean stacksEqual(StackTraceElement[] a, StackTraceElement[] b) {
if (a == null) {
return false;
}
if (a.length != b.length) {
return false;
}
for (int i = 0; i < a.length; ++i) {
if (!a[i].equals(b[i])) {
return false;
}
}
return true;
}
private void checkForDeadlock() {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long[] threadIds = threadBean.findMonitorDeadlockedThreads();
if (threadIds == null) {
return;
}
logConsumer.accept("Deadlock detected involving the following threads:");
for (ThreadInfo info : threadBean.getThreadInfo(threadIds, Integer.MAX_VALUE)) {
logConsumer.accept(
String.format(
"Thread #%d %s (%s) waiting on lock(%s) held by %s stack:%s",
info.getThreadId(),
info.getThreadName(),
info.getThreadState(),
info.getLockName(),
info.getLockOwnerName(),
stackTraceToString(info.getStackTrace())));
}
}
}