A process which occurs in the background fires callbacks to ask various questions.
In this case the question is "is it okay to migrate your data?", so I have to ask the user. Since we have to do all Swing work on the EDT, this ends up looking like this (I only removed comments, reference to our own convenience methods and the parameters to allowMigration()
- other than that, everything else is the same):
public class UserMigrationAcceptor implements MigrationAcceptor {
private final Window ownerWindow;
public UserMigrationAcceptor(Window ownerWindow) {
this.ownerWindow = ownerWindow;
}
// called on background worker thread
@Override
public boolean allowMigration() {
final AtomicBoolean result = new AtomicBoolean();
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
result.set(askUser());
}
});
} catch (InterruptedException e) {
Thread.currentThread.interrupt();
return false;
} catch (InvocationTargetException e) {
throw Throwables.propagate(e.getCause());
}
return result.get();
}
// called on EDT
private boolean askUser() {
int answer = JOptionPane.showConfirmDialog(ownerWindow, "...", "...",
JOptionPane.OK_CANCEL_OPTION);
return answer == JOptionPane.OK_OPTION;
}
}
What's happening is that in some situations, after confirming or cancelling the dialog which appears, Swing seems to get into the following state:
- the
JOptionPane
is no longer visible - there is nothing pending on the event queue
- the background thread is stuck inside
#invokeAndWait
, waiting forInvocationEvent#isDispatched()
to returntrue
.
Are we doing something wrong here, or am I looking at a bug in Swing/AWT?
The only other thing which might be worth noting is that this is the second level of modal dialogs. There is a modal dialog showing the progress of the operation and then this confirmation dialog has the progress dialog as its parent.
Update 1: Here's where the EDT is currently blocked:
java.lang.Thread.State: WAITING
at sun.misc.Unsafe.park(Unsafe.java:-1)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at java.awt.EventQueue.getNextEvent(EventQueue.java:543)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:211)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:154)
at java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:182)
at java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:221)
at java.security.AccessController.doPrivileged(AccessController.java:-1)
at java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:219)
at java.awt.Dialog.show(Dialog.java:1082)
at java.awt.Component.show(Component.java:1651)
at java.awt.Component.setVisible(Component.java:1603)
at java.awt.Window.setVisible(Window.java:1014)
at java.awt.Dialog.setVisible(Dialog.java:1005)
at com.acme.swing.progress.JProgressDialog$StateChangeListener$1.run(JProgressDialog.java:200)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:251)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:733)
at java.awt.EventQueue.access$200(EventQueue.java:103)
at java.awt.EventQueue$3.run(EventQueue.java:694)
at java.awt.EventQueue$3.run(EventQueue.java:692)
at java.security.AccessController.doPrivileged(AccessController.java:-1)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:703)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:154)
at java.awt.WaitDispatchSupport$2.run(WaitDispatchSupport.java:182)
at java.awt.WaitDispatchSupport$4.run(WaitDispatchSupport.java:221)
at java.security.AccessController.doPrivileged(AccessController.java:-1)
at java.awt.WaitDispatchSupport.enter(WaitDispatchSupport.java:219)
at java.awt.Dialog.show(Dialog.java:1082)
at javax.swing.JOptionPane.showOptionDialog(JOptionPane.java:870)
What's odd here is that the showOptionDialog()
at the bottom is the migration prompt, but the Dialog#setVisible
further up is the progress dialog. In other words, somehow sometimes the child dialog appears before the parent, and perhaps this is what is breaking Swing.
Update 2:
And indeed, I can make this happen in a test program without using any of our own code. Although the dialog positioning is different in the test program, it hangs in exactly the same way, only more reproducibly. gist
I just had this same issue in my own code.
Your call to
JOptionPane.showOptionDialog()
never returns, because while its event dispatch loop is sitting there waiting for user input, a timer (or something else) has fired and caused another modal dialog to install its own event loop. In your stack, the culprit isJProgressDialog$StateChangeListener$1.run()
, which you can then see launch its own event dispatch loop.Until your JProgressDialog is closed, it won't exit its loop, and the earlier call to JOptionPane.showOptionDialog() will never return.
This might not be obvious if the dialog parents seem to imply a hierarchy that isn't honored by the event queue.
Two solutions might be to either avoid a modal progress dialog, or show the progress dialog immediately. Launching a modal dialog off an event is only going to be a good idea if the rest of the event thread is happy to sit back and wait for it to be closed.
invokeAndWait
must be called out of EDT,carefully with
invokeAndWait
, because can freeze whole Swing GUI, locked by exceptions fromRepaintManager
, not in all cases only GUI is created, relayout, refreshed some of methods, whenrepaint()
is called from nested methodsfor
invokeAndWait
is required to testif (EventQueue.isDispatchThread()) {
/if (SwingUtilities.isEventDispatchThread()) {
on true from
isDispatchThread()
you can toresult.set(askUser());
) without any side effects, output is done on EDT, but is about good practicies to wrap insideinvokeLater
I seen some usages of
invokeAndWait
but only on application start_up, useinvokeLater()
insteadWith this kind of chaos, the first thing to suspect is that, somewhere, a Swing method is being called off the EventQueue (EDT). Swing often works very well with multiple threads, but then every once in a while this happens. Unfortunately, I know of no other way to to fix the problem than checking each and every swing method call and making sure it's on the EDT. (Note, there are one or two Swing methods that can run on other threads, such as
repaint
, but check the source code and Javadoc for each one to be sure.)