3

Various sporadic problems in the Swing application I maintain appear to be caused by the way it replaces the default AWT event queue with its own custom version using Toolkit.getDefaultToolkit().getSystemEventQueue().push(new AEventQueue()). See e.g. Threading and deadlock in Swing application. The problem described there has been resolved, but my tests (using FEST Swing) now tend to run into deadlock.

I suspect the best solution would be to replace the event queue at the beginning of the application initialization, before any Swing components are created. However, there are some dependencies that make that awkward so for the time being I am trying to find a safe way of "pushing" the new event queue after initialization, where it is currently done.

The two approaches I have tried are

  • push the new queue on the EDT using SwingUtilities.invokeLater();
  • push the new queue on the main thread after initialization, and after using invokeLater() to avoid deadlock with anything already started on the old EDT.

What I would expect, after reading https://stackoverflow.com/a/8965448/351885, is that the first approach might work in Java 7 but something like the second might be needed in Java 1.6. Indeed the second does work in Java 1.6, while in Java 7 both appear to complete successfully but run very very slowly. This may just be a FEST issue since the application itself seems quite responsive.

So I'm pretty much forced to use the second approach, which at least works in Java 1.6, but I would like to know - if there is a safer way to implement this, since it seems it might be vulnerable to a race condition if an event appears on the existing queue after invokeLater but before the new queue is created; - if there is a different approach I should use instead.

More detail

The first "solution" looks like this:

    initApplication();
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            Toolkit.getDefaultToolkit().getSystemEventQueue().push(new CustomEventQueue());
        }
    });

When compiled and run using Java 1.6, I don't understand what it is doing. It seems the thread is waiting for a lock that it already holds:

"AWT-EventQueue-1" prio=10 tid=0x00007f9808001000 nid=0x6628 in Object.wait() [0x00007f986aa72000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000007d9961cf0> (a atlantis.gui.AEventQueue)
    at java.lang.Object.wait(Object.java:502)
    at java.awt.EventQueue.getNextEvent(EventQueue.java:490)
    - locked <0x00000007d9961cf0> (a atlantis.gui.AEventQueue)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:247)

The second "solution" looks like this:

    initApplication();
    try {
        SwingUtilities.invokeAndWait(new Runnable() {
            @Override
            public void run() {
                logger.debug("Waiting for AWT event queue to be empty.");
            }
        });
    } catch (InterruptedException e) {
        logger.error("Interrupted while waiting for event queue.", e);
    } catch (InvocationTargetException e) {
        logger.error("Error while waiting for event queue.",e);
    }
    Toolkit.getDefaultToolkit().getSystemEventQueue().push(new CustomEventQueue());

As stated above, this seems to work OK in Java 1.6 but I'm not convinced it is really safe.

I haven't figured out what is happening when using Java 7, but the main thread seems to spend a long time sleeping the method org.fest.swing.timing.Pause.pause(), which is why I suspect this may be a FEST-specific problem.

Community
  • 1
  • 1
Ben
  • 2,348
  • 2
  • 19
  • 28

1 Answers1

3

Because I can't see reason to reset current EDT with fresh one, my questions are

1) are you got some of

  • Java deallock, outofmemory ...

  • RepaintManager exceptions,

2) basically you can

  • lock current EDT with Thread.sleep(int), with setVisible(false) for caused JComponent,

  • if is there EDT then you have to use invokeLater, if isn't active then you can choose betweens invokeLater of invokeAndWait

code

if (EventQueue.isDispatchThread()) {
    SwingUtilities.invokeLater(new Runnable() {

        @Override
        public void run() {
            //some stuff
        }
    });
} else {
    try {
        SwingUtilities.invokeAndWait(new Runnable() {

            @Override
            public void run() {
                //some stuff
            }
        });
    } catch (InterruptedException ex) {
        Logger.getLogger(IsThereEDT.class.getName()).log(Level.SEVERE, null, ex);
    } catch (InvocationTargetException ex) {
        Logger.getLogger(IsThereEDT.class.getName()).log(Level.SEVERE, null, ex);
    }
}

3) notice invokeAndWait must be called out of EDT, othewise caused EDT exceptions with deallock of current EDT

4) if isn't there active EDT, then there isn't reason push() something to the EventQueue

5) simple testing code for all above mentioned ..

import java.awt.EventQueue;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.*;

public class IsThereEDT {

    private ScheduledExecutorService scheduler;
    private AccurateScheduledRunnable periodic;
    private ScheduledFuture<?> periodicMonitor;
    private int taskPeriod = 30;
    private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
    private Date dateRun;
    private JFrame frame1 = new JFrame("Frame 1");

    public IsThereEDT() {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        periodic = new AccurateScheduledRunnable() {

            private final int ALLOWED_TARDINESS = 200;
            private int countRun = 0;
            private int countCalled = 0;
            private int maxCalled = 10;

            @Override
            public void run() {
                countCalled++;
                if (countCalled < maxCalled) {
                    if (countCalled % 3 == 0) {
                        /*if (EventQueue.isDispatchThread()) {
                            SwingUtilities.invokeLater(new Runnable() {

                                @Override
                                public void run() {
                                    //some stuff
                                }
                            });
                        } else {
                            try {
                                SwingUtilities.invokeAndWait(new Runnable() {

                                    @Override
                                    public void run() {
                                        //some stuff
                                    }
                                });
                            } catch (InterruptedException ex) {
                                Logger.getLogger(IsThereEDT.class.getName()).log(Level.SEVERE, null, ex);
                            } catch (InvocationTargetException ex) {
                                Logger.getLogger(IsThereEDT.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }*/
                        SwingUtilities.invokeLater(new Runnable() {

                            @Override
                            public void run() {
                                System.out.println("Push a new event to EDT");
                                frame1.repaint();
                                isThereReallyEDT();
                            }
                        });
                    } else {
                        if (this.getExecutionTime() < ALLOWED_TARDINESS) {
                            countRun++;
                            isThereReallyEDT(); // non on EDT
                        }
                    }
                } else {
                    System.out.println("Terminating this madness");
                    System.exit(0);
                }
            }
        };
        periodicMonitor = scheduler.scheduleAtFixedRate(periodic, 0, taskPeriod, TimeUnit.SECONDS);
        periodic.setThreadMonitor(periodicMonitor);
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                isThereReallyEDT();
                frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame1.getContentPane().add(new JLabel("Hello in frame 1"));
                frame1.pack();
                frame1.setLocation(100, 100);
                frame1.setVisible(true);
            }
        });
        try {
            Thread.sleep(500);
        } catch (InterruptedException ex) {
            Logger.getLogger(IsThereEDT.class.getName()).log(Level.SEVERE, null, ex);
        }
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame2 = new JFrame("Frame 2");
                frame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame2.getContentPane().add(new JLabel("Hello in frame 2"));
                frame2.pack();
                frame2.setLocation(200, 200);
                frame2.setVisible(true);
                isThereReallyEDT();
            }
        });
    }

    private void isThereReallyEDT() {
        dateRun = new java.util.Date();
        System.out.println("                         Time at : " + sdf.format(dateRun));
        if (EventQueue.isDispatchThread()) {
            System.out.println("EventQueue.isDispatchThread");
        } else {
            System.out.println("There isn't Live EventQueue.isDispatchThread, why any reason for that ");
        }
        if (SwingUtilities.isEventDispatchThread()) {
            System.out.println("SwingUtilities.isEventDispatchThread");
        } else {
            System.out.println("There isn't Live SwingUtilities.isEventDispatchThread, why any reason for that ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        IsThereEDT isdt = new IsThereEDT();
    }
}

abstract class AccurateScheduledRunnable implements Runnable {

    private ScheduledFuture<?> thisThreadsMonitor;

    public void setThreadMonitor(ScheduledFuture<?> monitor) {
        this.thisThreadsMonitor = monitor;
    }

    protected long getExecutionTime() {
        long delay = -1 * thisThreadsMonitor.getDelay(TimeUnit.MILLISECONDS);
        return delay;
    }
}
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • The custom event queue is used mainly to capture keystrokes globally so that various letters can be used as modifier keys. I'm not sure I understand all your suggestions but will read them more carefully. I'll add some detail to the question since it won't fit in a comment. – Ben Mar 26 '12 at 14:25