| package org.jdesktop.swinghelper.debug; |
| |
| import static java.util.concurrent.TimeUnit.MILLISECONDS; |
| |
| import java.awt.AWTEvent; |
| import java.awt.EventQueue; |
| import java.awt.Toolkit; |
| import java.util.ArrayDeque; |
| import java.util.Deque; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.ScheduledExecutorService; |
| import java.util.function.Consumer; |
| |
| /** |
| * Monitors the AWT event dispatch thread for events that take longer than a certain time to be |
| * dispatched. |
| * <p/> |
| * The principle is to record the time at which we start processing an event, and have another |
| * thread check frequently to see if we're still processing. If the other thread notices that we've |
| * been processing a single event for too long, it prints a stack trace showing what the event |
| * dispatch thread is doing, and continues to time it until it finally finishes. |
| * <p/> |
| * This is useful in determining what code is causing your Java application's GUI to be |
| * unresponsive. |
| * |
| * <p>The original source code can be found here<br> |
| * <a href="https://github.com/floscher/swinghelper/blob/master/src/java/org/jdesktop/swinghelper/debug/EventDispatchThreadHangMonitor.java"> |
| * EventDispatchThreadHangMonitor.java</a> |
| * </p> |
| */ |
| public final class EventDispatchThreadHangMonitor extends EventQueue implements Runnable { |
| |
| // Time to wait between checks that the event dispatch thread isn't hung. |
| private static final long CHECK_INTERVAL_MS = 100; |
| |
| // Maximum time we won't warn about. This used to be 500 ms, but 1.5 on |
| // late-2004 hardware isn't really up to it; there are too many parts of |
| // the JDK that can go away for that long (often code that has to be |
| // called on the event dispatch thread, like font loading). |
| static final long UNREASONABLE_DISPATCH_DURATION_MS = 1000; |
| |
| // The currently outstanding event dispatches. The implementation of |
| // modal dialogs is a common cause for multiple outstanding dispatches. |
| private final Deque<DispatchInfo> dispatches = new ArrayDeque<>(); |
| |
| // The scheduler to regularly check whether a hang event is detected or not. |
| private final ScheduledExecutorService scheduler = |
| Executors.newSingleThreadScheduledExecutor( |
| runnable -> { |
| Thread t = new Thread(runnable); |
| t.setDaemon(true); |
| return t; |
| }); |
| |
| private final Consumer<String> logConsumer; |
| |
| /** |
| * Constructs {@link EventDispatchThreadHangMonitor}. |
| */ |
| public EventDispatchThreadHangMonitor() { |
| this(System.out::println); |
| } |
| |
| /** |
| * Constructs {@link EventDispatchThreadHangMonitor}. |
| * |
| * @param logConsumer the custom log consumer |
| */ |
| public EventDispatchThreadHangMonitor(Consumer<String> logConsumer) { |
| this.logConsumer = logConsumer; |
| } |
| |
| /** |
| * Sets up hang detection for the event dispatch thread. |
| */ |
| public void initMonitoring() { |
| Toolkit.getDefaultToolkit().getSystemEventQueue().push(this); |
| Future<?> unused = scheduler.scheduleWithFixedDelay(this, 0, CHECK_INTERVAL_MS, MILLISECONDS); |
| } |
| |
| @Override |
| protected void dispatchEvent(AWTEvent event) { |
| DispatchInfo currentDispatchInfo = new DispatchInfo(logConsumer); |
| try { |
| synchronized (dispatches) { |
| dispatches.addLast(currentDispatchInfo); |
| } |
| super.dispatchEvent(event); |
| } finally { |
| synchronized (dispatches) { |
| // We've finished the most nested dispatch, and don't need it any longer. |
| dispatches.removeLast(); |
| currentDispatchInfo.dispose(); |
| |
| // The other dispatches, which have been waiting, need to be credited extra time. |
| // We do this rather simplistically by pretending they've just been redispatched. |
| Thread currentEventDispatchThread = Thread.currentThread(); |
| for (DispatchInfo dispatchInfo : dispatches) { |
| if (dispatchInfo.eventDispatchThread == currentEventDispatchThread) { |
| dispatchInfo.lastDispatchTimeMillis = System.currentTimeMillis(); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void run() { |
| synchronized (dispatches) { |
| if (!dispatches.isEmpty()) { |
| // Only the most recent dispatch can be hung; nested dispatches |
| // by their nature cause the outer dispatch pump to be suspended. |
| dispatches.getLast().checkForHang(); |
| } |
| } |
| } |
| } |