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