blob: dbc8295d972a27f269af2465693729baf63751df [file] [log] [blame]
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();
}
}
}
}