/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ package de.cismet.tools.gui; import java.awt.AWTEvent; import java.awt.EventQueue; import java.awt.Toolkit; import java.io.PrintStream; import java.util.Timer; import java.util.TimerTask; /** * 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> * * <p>This is useful in determining what code is causing your Java application's GUI to be unresponsive.</p> * * @author Elliott Hughes <enh@jessies.org> * @version $Revision$, $Date$ */ public final class EventDispatchThreadHangMonitor extends EventQueue { //~ Static fields/initializers --------------------------------------------- private static final transient org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger( EventDispatchThreadHangMonitor.class); private static final EventQueue INSTANCE = new EventDispatchThreadHangMonitor(); // 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. private static final long UNREASONABLE_DISPATCH_DURATION_MS = 350; // Used as the value of startedLastEventDispatchAt when we're not in // the middle of event dispatch. private static final long NO_CURRENT_EVENT = 0; //~ Instance fields -------------------------------------------------------- // When we started dispatching the current event, in milliseconds. private long startedLastEventDispatchAt = NO_CURRENT_EVENT; // Have we already dumped a stack trace for the current event dispatch? private boolean reportedHang = false; // The event dispatch thread, for the purpose of getting stack traces. private Thread eventDispatchThread = null; //~ Constructors ----------------------------------------------------------- /** * Creates a new EventDispatchThreadHangMonitor object. */ private EventDispatchThreadHangMonitor() { initTimer(); } //~ Methods ---------------------------------------------------------------- /** * Sets up a timer to check for hangs frequently. */ private void initTimer() { final long initialDelayMs = 0; final boolean isDaemon = true; final Timer timer = new Timer("EventDispatchThreadHangMonitor", isDaemon); timer.schedule(new HangChecker(), initialDelayMs, CHECK_INTERVAL_MS); } /** * Returns how long we've been processing the current event (in milliseconds). * * @return DOCUMENT ME! */ private long timeSoFar() { final long currentTime = System.currentTimeMillis(); return (currentTime - startedLastEventDispatchAt); } /** * Sets up hang detection for the event dispatch thread. */ public static void initMonitoring() { Toolkit.getDefaultToolkit().getSystemEventQueue().push(INSTANCE); } /** * Overrides EventQueue.dispatchEvent to call our pre and post hooks either side of the system's event dispatch * code. * * @param event DOCUMENT ME! */ @Override protected void dispatchEvent(final AWTEvent event) { preDispatchEvent(); super.dispatchEvent(event); postDispatchEvent(); } /** * Stores the time at which we started processing the current event. */ private synchronized void preDispatchEvent() { if (eventDispatchThread == null) { // I don't know of any API for getting the event dispatch thread, // but we can assume that it's the current thread if we're in the // middle of dispatching an AWT event... eventDispatchThread = Thread.currentThread(); } reportedHang = false; startedLastEventDispatchAt = System.currentTimeMillis(); } /** * Reports the end of any ongoing hang, and notes that we're no longer processing an event. */ private synchronized void postDispatchEvent() { if (reportedHang) { System.out.println("--- event dispatch thread unstuck after " + timeSoFar() + " ms."); log.fatal("--- event dispatch thread unstuck after " + timeSoFar() + " ms."); } startedLastEventDispatchAt = NO_CURRENT_EVENT; } //~ Inner Classes ---------------------------------------------------------- /** * DOCUMENT ME! * * @version $Revision$, $Date$ */ private class HangChecker extends TimerTask { //~ Methods ------------------------------------------------------------ /** * DOCUMENT ME! */ @Override public void run() { // Synchronize on the outer class, because that's where all // the state lives. synchronized (INSTANCE) { checkForHang(); } } /** * DOCUMENT ME! */ private void checkForHang() { if (startedLastEventDispatchAt == NO_CURRENT_EVENT) { // We don't destroy the timer when there's nothing happening // because it would mean a lot more work on every single AWT // event that gets dispatched. return; } if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) { reportHang(); } } /** * DOCUMENT ME! */ private void reportHang() { if (reportedHang) { // Don't keep reporting the same hang every 100 ms. return; } reportedHang = true; System.out.println("--- event dispatch thread stuck processing event for " + timeSoFar() + " ms:"); final StackTraceElement[] stackTrace = eventDispatchThread.getStackTrace(); printStackTrace(System.out, stackTrace); final Throwable t = new Throwable("event dispatch thread stuck"); t.setStackTrace(stackTrace); log.fatal("--- event dispatch thread stuck processing event for " + timeSoFar() + " ms:", t); } /** * DOCUMENT ME! * * @param out DOCUMENT ME! * @param stackTrace DOCUMENT ME! */ private void printStackTrace(final PrintStream out, final StackTraceElement[] stackTrace) { // We know that it's not interesting to show any code above where // we get involved in event dispatch, so we stop printing the stack // trace when we get as far back as our code. final String ourEventQueueClassName = EventDispatchThreadHangMonitor.class.getName(); for (final StackTraceElement stackTraceElement : stackTrace) { if (stackTraceElement.getClassName().equals(ourEventQueueClassName)) { return; } out.println(" " + stackTraceElement); } } } }