Code refactor and update the implementation of isWaitingForNextEvent
Change-Id: Ic6f4db7cc91c2e4c18d921704f638cfdc7299e2c
diff --git a/src/main/java/org/jdesktop/swinghelper/debug/DispatchInfo.java b/src/main/java/org/jdesktop/swinghelper/debug/DispatchInfo.java
new file mode 100644
index 0000000..f675efc
--- /dev/null
+++ b/src/main/java/org/jdesktop/swinghelper/debug/DispatchInfo.java
@@ -0,0 +1,146 @@
+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())));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/jdesktop/swinghelper/debug/EventDispatchThreadHangMonitor.java b/src/main/java/org/jdesktop/swinghelper/debug/EventDispatchThreadHangMonitor.java
index 47541af..dbc8295 100644
--- a/src/main/java/org/jdesktop/swinghelper/debug/EventDispatchThreadHangMonitor.java
+++ b/src/main/java/org/jdesktop/swinghelper/debug/EventDispatchThreadHangMonitor.java
@@ -1,463 +1,118 @@
-/*
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
package org.jdesktop.swinghelper.debug;
-import java.awt.*;
-import java.awt.event.*;
-import java.lang.management.*;
-import java.util.*;
-import java.util.Timer;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import javax.swing.*;
+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.
+ * 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.
+ * 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 blog can be found here<br>
- * <a href="http://elliotth.blogspot.com/2005/05/automatically-detecting-awt-event.html">
- * Automatically detecting AWT event dispatch thread hangs</a>
- * </p>
+ * This is useful in determining what code is causing your Java application's GUI to be
+ * unresponsive.
*
- * @author Elliott Hughes <enh@jessies.org>
- *
- * Advice, bug fixes, and test cases from
- * Alexander Potochkin and Oleg Sukhodolsky.
- *
- * https://swinghelper.dev.java.net/
+ * <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 {
- private static final EventDispatchThreadHangMonitor INSTANCE = new EventDispatchThreadHangMonitor();
+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;
+ // 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).
- private static final long UNREASONABLE_DISPATCH_DURATION_MS = 1000;
+ // 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;
- // Help distinguish multiple hangs in the log, and match start and end too.
- // Only access this via getNewHangNumber.
- private static int hangCount = 0;
+ // 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<>();
- // Prevents us complaining about hangs during start-up, which are probably
- // the JVM vendor's fault.
- private boolean haveShownSomeComponent = false;
+ // 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;
+ });
- // The currently outstanding event dispatches. The implementation of
- // modal dialogs is a common cause for multiple outstanding dispatches.
- private final LinkedList<DispatchInfo> dispatches = new LinkedList<DispatchInfo>();
+ private final Consumer<String> logConsumer;
- private static class DispatchInfo {
- // The last-dumped hung stack trace for this dispatch.
- private StackTraceElement[] lastReportedStack;
- // If so; what was the identifying hang number?
- private int hangNumber;
+ /**
+ * Constructs {@link EventDispatchThreadHangMonitor}.
+ */
+ public EventDispatchThreadHangMonitor() {
+ this(System.out::println);
+ }
- // The EDT for this dispatch (for the purpose of getting stack traces).
- // 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...
- // We can't cache this because the EDT can die and be replaced by a
- // new EDT if there's an uncaught exception.
- private final Thread eventDispatchThread = Thread.currentThread();
+ /**
+ * Constructs {@link EventDispatchThreadHangMonitor}.
+ *
+ * @param logConsumer the custom log consumer
+ */
+ public EventDispatchThreadHangMonitor(Consumer<String> logConsumer) {
+ this.logConsumer = logConsumer;
+ }
- // The last time in milliseconds at which we saw a dispatch on the above thread.
- private long lastDispatchTimeMillis = System.currentTimeMillis();
+ /**
+ * 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);
+ }
- public DispatchInfo() {
- // All initialization is done by the field initializers.
+ @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();
+ }
}
-
- public void checkForHang() {
- if (timeSoFar() > UNREASONABLE_DISPATCH_DURATION_MS) {
- examineHang();
- }
- }
-
- // 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;
- }
-
- // Checks whether the given stack looks like it's waiting for another event.
- // This relies on JDK implementation details.
- private boolean isWaitingForNextEvent(StackTraceElement[] currentStack) {
- return stackTraceElementIs(currentStack[0], "java.lang.Object", "wait", true) && stackTraceElementIs(currentStack[1], "java.lang.Object", "wait", false) && stackTraceElementIs(currentStack[2], "java.awt.EventQueue", "getNextEvent", false);
- }
-
- 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)) {
- // Don't keep reporting the same hang every time the timer goes off.
- return;
- }
-
- hangNumber = getNewHangNumber();
- String stackTrace = stackTraceToString(currentStack);
- lastReportedStack = currentStack;
- Log.warn("(hang #" + hangNumber + ") event dispatch thread stuck processing event for " + timeSoFar() + " ms:" + stackTrace);
- checkForDeadlock();
- }
-
- private static 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]) == false) {
- return false;
- }
- }
- return true;
- }
-
- /**
- * Returns how long this dispatch has been going on (in milliseconds).
- */
- private long timeSoFar() {
- return (System.currentTimeMillis() - lastDispatchTimeMillis);
- }
-
- public void dispose() {
- if (lastReportedStack != null) {
- Log.warn("(hang #" + hangNumber + ") event dispatch thread unstuck after " + timeSoFar() + " ms.");
- }
- }
+ }
}
+ }
- private EventDispatchThreadHangMonitor() {
- initTimer();
+ @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();
+ }
}
-
- /**
- * Sets up a timer to check for hangs frequently.
- */
- private void initTimer() {
- final long initialDelayMs = 0;
- final boolean isDaemon = true;
- Timer timer = new Timer("EventDispatchThreadHangMonitor", isDaemon);
- timer.schedule(new HangChecker(), initialDelayMs, CHECK_INTERVAL_MS);
- }
-
- private class HangChecker extends TimerTask {
- @Override
- public void run() {
- synchronized (dispatches) {
- if (dispatches.isEmpty() || !haveShownSomeComponent) {
- // Nothing to do.
- // 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;
- }
- // Only the most recent dispatch can be hung; nested dispatches
- // by their nature cause the outer dispatch pump to be suspended.
- dispatches.getLast().checkForHang();
- }
- }
- }
-
- /**
- * 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.
- */
- @Override
- protected void dispatchEvent(AWTEvent event) {
- try {
- preDispatchEvent();
- super.dispatchEvent(event);
- } finally {
- postDispatchEvent();
- if (!haveShownSomeComponent &&
- event instanceof WindowEvent && event.getID() == WindowEvent.WINDOW_OPENED) {
- haveShownSomeComponent = true;
- }
- }
- }
-
- private void debug(String which) {
- if (false) {
- for (int i = dispatches.size(); i >= 0; --i) {
- System.out.print(' ');
- }
- System.out.println(which);
- }
- }
-
- /**
- * Starts tracking a dispatch.
- */
- private synchronized void preDispatchEvent() {
- debug("pre");
- synchronized (dispatches) {
- dispatches.addLast(new DispatchInfo());
- }
- }
-
- /**
- * Stops tracking a dispatch.
- */
- private synchronized void postDispatchEvent() {
- synchronized (dispatches) {
- // We've finished the most nested dispatch, and don't need it any longer.
- DispatchInfo justFinishedDispatch = dispatches.removeLast();
- justFinishedDispatch.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();
- }
- }
- }
- debug("post");
- }
-
- private static void checkForDeadlock() {
- ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
- long[] threadIds = threadBean.findMonitorDeadlockedThreads();
- if (threadIds == null) {
- return;
- }
- Log.warn("deadlock detected involving the following threads:");
- ThreadInfo[] threadInfos = threadBean.getThreadInfo(threadIds, Integer.MAX_VALUE);
- for (ThreadInfo info : threadInfos) {
- Log.warn("Thread #" + info.getThreadId() + " " + info.getThreadName() +
- " (" + info.getThreadState() + ") waiting on " + info.getLockName() +
- " held by " + info.getLockOwnerName() + stackTraceToString(info.getStackTrace()));
- }
- }
-
- private static 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" + indentation + stackTraceElement);
- }
- return result.toString();
- }
-
- private synchronized static int getNewHangNumber() {
- return ++hangCount;
- }
-
- public static void main(String[] args) {
- initMonitoring();
- //special case for deadlock test
- if (args.length > 0 && "deadlock".equals(args[0])) {
- EventDispatchThreadHangMonitor.INSTANCE.haveShownSomeComponent = true;
- Tests.runDeadlockTest();
- return;
- }
- Tests.main(args);
- }
-
- private static class Tests {
- public static void main(final String[] args) {
-
- java.awt.EventQueue.invokeLater(new Runnable() {
- public void run() {
- for (String arg : args) {
- final JFrame frame = new JFrame();
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.setLocationRelativeTo(null);
- if (arg.equals("exception")) {
- runExceptionTest(frame);
- } else if (arg.equals("focus")) {
- runFocusTest(frame);
- } else if (arg.equals("modal-hang")) {
- runModalTest(frame, true);
- } else if (arg.equals("modal-no-hang")) {
- runModalTest(frame, false);
- } else {
- System.err.println("unknown regression test '" + arg + "'");
- System.exit(1);
- }
- frame.pack();
- frame.setVisible(true);
- }
- }
- });
- }
-
- private static void runDeadlockTest() {
- class Locker {
- private Locker locker;
-
- public void setLocker(Locker locker) {
- this.locker = locker;
- }
-
- public synchronized void tryToDeadlock() {
- locker.toString();
- }
-
- public synchronized String toString() {
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return super.toString();
- }
- }
- final Locker one = new Locker();
- final Locker two = new Locker();
- one.setLocker(two);
- two.setLocker(one);
-
- //Deadlock expected here:
- for (int i = 0; i < 100; i++) {
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- one.tryToDeadlock();
- }
- });
- two.tryToDeadlock();
- }
- }
-
- // If we don't do our post-dispatch activity in a finally block, we'll
- // report bogus hangs.
- private static void runExceptionTest(final JFrame frame) {
- JButton button = new JButton("Throw Exception");
- button.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- // This shouldn't cause us to report a hang.
- throw new RuntimeException("Nobody expects the Spanish Inquisition!");
- }
- });
- frame.add(button);
- }
-
- // A demonstration of nested calls to dispatchEvent caused by SequencedEvent.
- private static void runFocusTest(final JFrame frame) {
- final JDialog dialog = new JDialog(frame, "Non-Modal Dialog");
- dialog.add(new JLabel("Close me!"));
- dialog.pack();
- dialog.setLocationRelativeTo(frame);
- dialog.addWindowFocusListener(new WindowAdapter() {
- public void windowGainedFocus(WindowEvent e) {
- System.out.println("FocusTest.windowGainedFocus");
- // If you don't cope with nested calls to dispatchEvent, you won't detect this.
- // See java.awt.SequencedEvent for an example.
- sleep(2500);
- }
- });
- JButton button = new JButton("Show Non-Modal Dialog");
- button.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- dialog.setVisible(true);
- }
- });
- frame.add(button);
- }
-
- // A demonstration of the problems of dealing with modal dialogs.
- private static void runModalTest(final JFrame frame, final boolean shouldSleep) {
- System.out.println(shouldSleep ? "Expect hangs!" : "There should be no hangs...");
- JButton button = new JButton("Show Modal Dialog");
- button.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (shouldSleep) {
- sleep(2500); // This is easy.
- }
- JDialog dialog = new JDialog(frame, "Modal dialog", true);
- dialog.setLayout(new FlowLayout());
- dialog.add(new JLabel("Close this dialog!"));
- final JLabel label = new JLabel(" ");
- dialog.add(label);
- dialog.pack();
- dialog.setLocation(frame.getX() - 100, frame.getY());
-
- // Make sure the new event pump has some work to do, each unit of which is insufficient to cause a hang.
- new Thread(new Runnable() {
- public void run() {
- for (int i = 0; i <= 100000; ++i) {
- final int value = i;
- EventQueue.invokeLater(new Runnable() {
- public void run() {
- label.setText(Integer.toString(value));
- }
- });
- }
- }
- }).start();
-
- dialog.setVisible(true);
-
- if (shouldSleep) {
- sleep(2500); // If you don't distinguish different stack traces, you won't report this.
- }
- }
- });
- frame.add(button);
- }
-
- private static void sleep(long ms) {
- try {
- System.out.println("Sleeping for " + ms + " ms on " + Thread.currentThread() + "...");
- Thread.sleep(ms);
- System.out.println("Finished sleeping...");
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- }
- }
-
- private static class Log {
- public static void warn(String str) {
- System.out.println(str);
- }
- }
-}
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/jdesktop/swinghelper/debug/LICENSE b/src/main/java/org/jdesktop/swinghelper/debug/LICENSE
new file mode 100644
index 0000000..de3016f
--- /dev/null
+++ b/src/main/java/org/jdesktop/swinghelper/debug/LICENSE
@@ -0,0 +1,13 @@
+ This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
\ No newline at end of file
diff --git a/src/main/java/org/jdesktop/swinghelper/debug/Tests.java b/src/main/java/org/jdesktop/swinghelper/debug/Tests.java
new file mode 100644
index 0000000..e9e314a
--- /dev/null
+++ b/src/main/java/org/jdesktop/swinghelper/debug/Tests.java
@@ -0,0 +1,190 @@
+package org.jdesktop.swinghelper.debug;
+
+import java.awt.EventQueue;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+
+final class Tests {
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ return;
+ }
+ String mode = args[0];
+
+ new EventDispatchThreadHangMonitor().initMonitoring();
+
+ if ("deadlock".equals(mode)) {
+ runDeadlockTest();
+ return;
+ }
+
+ EventQueue.invokeLater(
+ () -> {
+ final JFrame frame = new JFrame();
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.setLocationRelativeTo(null);
+ switch (mode) {
+ case "exception":
+ runExceptionTest(frame);
+ break;
+ case "focus":
+ runFocusTest(frame);
+ break;
+ case "modal-hang":
+ runModalTest(frame, true);
+ break;
+ case "modal-no-hang":
+ runModalTest(frame, false);
+ break;
+ default:
+ System.err.println("unknown regression test '" + mode + "'");
+ System.exit(1);
+ }
+ frame.pack();
+ frame.setVisible(true);
+ });
+ }
+
+ public static void runDeadlockTest() {
+ class Locker {
+
+ private Locker locker;
+
+ public void setLocker(Locker locker) {
+ this.locker = locker;
+ }
+
+ public synchronized void tryToDeadlock() {
+ String unused = locker.toString();
+ }
+
+ @Override
+ @SuppressWarnings("CatchAndPrintStackTrace")
+ public synchronized String toString() {
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ return super.toString();
+ }
+ }
+
+ final Locker one = new Locker();
+ final Locker two = new Locker();
+ one.setLocker(two);
+ two.setLocker(one);
+
+ // Deadlock expected here:
+ for (int i = 0; i < 100; i++) {
+ SwingUtilities.invokeLater(one::tryToDeadlock);
+ two.tryToDeadlock();
+ }
+ }
+
+ // If we don't do our post-dispatch activity in a finally block, we'll
+ // report bogus hangs.
+ private static void runExceptionTest(final JFrame frame) {
+ JButton button = new JButton("Throw Exception");
+ button.addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // This shouldn't cause us to report a hang.
+ throw new IllegalStateException("Nobody expects the Spanish Inquisition!");
+ }
+ });
+ frame.add(button);
+ }
+
+ // A demonstration of nested calls to dispatchEvent caused by SequencedEvent.
+ private static void runFocusTest(final JFrame frame) {
+ final JDialog dialog = new JDialog(frame, "Non-Modal Dialog");
+ dialog.add(new JLabel("Close me!"));
+ dialog.pack();
+ dialog.setLocationRelativeTo(frame);
+ dialog.addWindowFocusListener(
+ new WindowAdapter() {
+ @Override
+ public void windowGainedFocus(WindowEvent e) {
+ System.out.println("FocusTest.windowGainedFocus");
+ // If you don't cope with nested calls to dispatchEvent, you won't detect this.
+ // See java.awt.SequencedEvent for an example.
+ sleep(2500);
+ }
+ });
+ JButton button = new JButton("Show Non-Modal Dialog");
+ button.addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ dialog.setVisible(true);
+ }
+ });
+ frame.add(button);
+ }
+
+ // A demonstration of the problems of dealing with modal dialogs.
+ private static void runModalTest(final JFrame frame, final boolean shouldSleep) {
+ System.out.println(shouldSleep ? "Expect hangs!" : "There should be no hangs...");
+ JButton button = new JButton("Show Modal Dialog");
+ button.addActionListener(
+ new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (shouldSleep) {
+ sleep(2500); // This is easy.
+ }
+ JDialog dialog = new JDialog(frame, "Modal dialog", /* modal= */ true);
+ dialog.setLayout(new FlowLayout());
+ dialog.add(new JLabel("Close this dialog!"));
+ final JLabel label = new JLabel(" ");
+ dialog.add(label);
+ dialog.pack();
+ dialog.setLocation(frame.getX() - 100, frame.getY());
+
+ // Make sure the new event pump has some work to do, each unit of which is insufficient
+ // to cause a hang.
+ new Thread(
+ () -> {
+ for (int i = 0; i <= 100000; ++i) {
+ final int value = i;
+ EventQueue.invokeLater(() -> label.setText(Integer.toString(value)));
+ }
+ })
+ .start();
+
+ dialog.setVisible(true);
+
+ if (shouldSleep) {
+ sleep(
+ 2500); // If you don't distinguish different stack traces, you won't report this.
+ }
+ }
+ });
+ frame.add(button);
+ }
+
+ @SuppressWarnings("CatchAndPrintStackTrace")
+ private static void sleep(long ms) {
+ try {
+ System.out.println("Sleeping for " + ms + " ms on " + Thread.currentThread() + "...");
+ Thread.sleep(ms);
+ System.out.println("Finished sleeping...");
+ } catch (InterruptedException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ private Tests() {
+ }
+}
\ No newline at end of file