| 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()))); |
| } |
| } |
| } |