javaswingfreezeinvokeandwait

invokeAndWait seems to cause the application to freeze intermittently


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:

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


Solution

  • 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 is JProgressDialog$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.