Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Orphaned event (EventQueue.dummyRunnable) in an eventQueue will break waitForIdle. #243

Open
cpholt opened this issue Dec 20, 2019 · 1 comment

Comments

@cpholt
Copy link

cpholt commented Dec 20, 2019

I've seen a sporadic failure where waitForIdle gets into a state where it times out after 10 seconds. The application is actually idle when this happens. This causes a lot of grief for us because it makes all the test run at a snails pace (20 minutes instead of 20 seconds).

2 important conditions for this failure:

  1. install a custom eventQueue via Toolkit.getDefaultToolkit().getSystemEventQueue().push()
  2. create and hide a Window first. (this is a splash screen in our real app).

After a lot of debugging by adding logs to BasicRobot waitForIdle, I manage to catch it where the custom event queue was empty, but it was waiting for a java.awt.EventQueue to drain. It contained an InvocationEvent with this runnable. "runnable=java.awt.EventQueue$1@2d1630f3". Looking into that lead me to the first anonymous class in EventQueue, which is the member variable "dummyRunnable". That dummyRunnable is used in push/pop to wake up the EDT.

I theorized that if the EDT was already awake when the new Queue was pushed, then that event could be left.

That didn't fail initially when I only had 1 window and tried the push from within an InvokeAndWait. I then expanded the test to create a window, hide it, push a new queue and then wait.

That reproduces the failure. Simple test case below. I think that the first queue (for the hidden window?) is really dead? maybe it should be removed from the "windowMonitor.allEventQueues()" in BasicRobot? I got this far, and now I'm at a loss as to how to proceed.

package ajs.eq.fail;

import java.awt.EventQueue;
import java.awt.Toolkit;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import org.assertj.swing.core.BasicRobot;

public class AjsEqFail {

    private final static EventQueue myQueue = new EventQueue() {
        
    };
    public static void main(String[] args) {
        BasicRobot r = (BasicRobot) BasicRobot.robotWithCurrentAwtHierarchy();
        r.settings().simpleWaitForIdle(false);
        final JFrame f = new JFrame();
        f.setBounds(10,10,300,300);
        f.setVisible(true);
        r.waitForIdle();
        System.out.println("waited");
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("Hi3");
                        }
                    });
                    System.out.println("Hi1");
                    f.setVisible(false);
                    Toolkit.getDefaultToolkit().getSystemEventQueue().push(myQueue);
                    JFrame f2 = new JFrame();
                    f2.setVisible(true);
                    f2.setBounds(10,10,300,300);
                    System.out.println("Hi2");
                }
            });
        } catch (InterruptedException ex) {
            Logger.getLogger(AjsEqFail.class.getName()).log(Level.SEVERE, null, ex);
        } catch (InvocationTargetException ex) {
            Logger.getLogger(AjsEqFail.class.getName()).log(Level.SEVERE, null, ex);
        }
        r.waitForIdle(); //<-------------this times eventually (30 seconds?) but should be immediate.
        System.out.println("waited2");
    }
    
}

@henri-tremblay
Copy link

I confirm this is a problem. It seems to be a known problem since CustomEventQueue_Test reproduces it. r.settings().simpleWaitForIdle(true); seems to be the fix. Is it the official fix? What are the drawbacks?

It brings many question. First, in this code:

      Collection<EventQueue> queues = windowMonitor.allEventQueues();
      if (queues.size() == 1) {
        waitForIdle(checkNotNull(toolkit.getSystemEventQueue()));
        return;
      }
      // FIXME this resurrects dead event queues
      for (EventQueue queue : queues) {
        waitForIdle(checkNotNull(queue));
      }

Why do we need to look at anything else than the system event queue? Then, assuming this is needed, the problem is indeed, we keep looking at queues that have been pushed down the queue stack. One solution could be to check if the event is EventQueue.dummyRunnable. If this is the case, we are in a dead queue. This is the ugly pseudo-code:

    Field dummyRunnableField = EventQueue.class.getDeclaredField("dummyRunnable");
    dummyRunnableField.setAccessible(true);
    Runnable runnable = dummyRunnableField.get(null);
    
    do {
      // Timed out waiting for idle
      if (postInvocationEvent(eventQueue, idleTimeout)) {
        break;
      }
      // Timed out waiting for idle event queue
      if (currentTimeMillis() - start > idleTimeout) {
        break;
      }
      // Force a yield
      pause();
      // Look if this is a dead queue
      AWTEvent event = eventQueue.peekEvent();
      // Abbot: this does not detect invocation events (i.e. what gets posted with EventQueue.invokeLater), so if
      // someone is repeatedly posting one, we might get stuck. Not too worried, since if a Runnable keeps calling
      // invokeLater on itself, *nothing* else gets much chance to run, so it seems to be a bad programming practice.
      if (event == null) {
        break;
      }
      if (event instanceof InvocationEvent && ((InvocationEvent) event).runnable == runnable) {
        // remove this queue from the list. It's a dead queue. Or just ignore and get out.
      }
    } while (true);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants