6980209: Make tracking SecondaryLoop.enter/exit methods easier
authorssadetsky
Fri, 08 May 2015 15:37:38 +0300
changeset 30916 8d4095a7bf7a
parent 30500 9d40ba747a9a
child 30917 511aae7fee1a
6980209: Make tracking SecondaryLoop.enter/exit methods easier Reviewed-by: serb, ant
jdk/src/java.desktop/share/classes/java/awt/WaitDispatchSupport.java
jdk/test/java/awt/EventQueue/6980209/bug6980209.java
--- a/jdk/src/java.desktop/share/classes/java/awt/WaitDispatchSupport.java	Wed May 06 18:30:31 2015 +0300
+++ b/jdk/src/java.desktop/share/classes/java/awt/WaitDispatchSupport.java	Fri May 08 15:37:38 2015 +0300
@@ -65,6 +65,7 @@
 
     private AtomicBoolean keepBlockingEDT = new AtomicBoolean(false);
     private AtomicBoolean keepBlockingCT = new AtomicBoolean(false);
+    private AtomicBoolean afterExit = new AtomicBoolean(false);
 
     private static synchronized void initializeTimer() {
         if (timer == null) {
@@ -114,7 +115,7 @@
                 }
                 boolean extEvaluate =
                     (extCondition != null) ? extCondition.evaluate() : true;
-                if (!keepBlockingEDT.get() || !extEvaluate) {
+                if (!keepBlockingEDT.get() || !extEvaluate || afterExit.get()) {
                     if (timerTask != null) {
                         timerTask.cancel();
                         timerTask = null;
@@ -174,110 +175,116 @@
             log.fine("The secondary loop is already running, aborting");
             return false;
         }
+        try {
+            if (afterExit.get()) {
+                log.fine("Exit was called already, aborting");
+                return false;
+            }
 
-        final Runnable run = new Runnable() {
-            public void run() {
-                log.fine("Starting a new event pump");
-                if (filter == null) {
-                    dispatchThread.pumpEvents(condition);
-                } else {
-                    dispatchThread.pumpEventsForFilter(condition, filter);
+            final Runnable run = new Runnable() {
+                public void run() {
+                    log.fine("Starting a new event pump");
+                    if (filter == null) {
+                        dispatchThread.pumpEvents(condition);
+                    } else {
+                        dispatchThread.pumpEventsForFilter(condition, filter);
+                    }
                 }
-            }
-        };
+            };
+
+            // We have two mechanisms for blocking: if we're on the
+            // dispatch thread, start a new event pump; if we're
+            // on any other thread, call wait() on the treelock
 
-        // We have two mechanisms for blocking: if we're on the
-        // dispatch thread, start a new event pump; if we're
-        // on any other thread, call wait() on the treelock
-
-        Thread currentThread = Thread.currentThread();
-        if (currentThread == dispatchThread) {
-            if (log.isLoggable(PlatformLogger.Level.FINEST)) {
-                log.finest("On dispatch thread: " + dispatchThread);
-            }
-            if (interval != 0) {
+            Thread currentThread = Thread.currentThread();
+            if (currentThread == dispatchThread) {
                 if (log.isLoggable(PlatformLogger.Level.FINEST)) {
-                    log.finest("scheduling the timer for " + interval + " ms");
+                    log.finest("On dispatch thread: " + dispatchThread);
+                }
+                if (interval != 0) {
+                    if (log.isLoggable(PlatformLogger.Level.FINEST)) {
+                        log.finest("scheduling the timer for " + interval + " ms");
+                    }
+                    timer.schedule(timerTask = new TimerTask() {
+                        @Override
+                        public void run() {
+                            if (keepBlockingEDT.compareAndSet(true, false)) {
+                                wakeupEDT();
+                            }
+                        }
+                    }, interval);
+                }
+                // Dispose SequencedEvent we are dispatching on the current
+                // AppContext, to prevent us from hang - see 4531693 for details
+                SequencedEvent currentSE = KeyboardFocusManager.
+                        getCurrentKeyboardFocusManager().getCurrentSequencedEvent();
+                if (currentSE != null) {
+                    if (log.isLoggable(PlatformLogger.Level.FINE)) {
+                        log.fine("Dispose current SequencedEvent: " + currentSE);
+                    }
+                    currentSE.dispose();
                 }
-                timer.schedule(timerTask = new TimerTask() {
-                    @Override
-                    public void run() {
-                        if (keepBlockingEDT.compareAndSet(true, false)) {
-                            wakeupEDT();
+                // In case the exit() method is called before starting
+                // new event pump it will post the waking event to EDT.
+                // The event will be handled after the new event pump
+                // starts. Thus, the enter() method will not hang.
+                //
+                // Event pump should be privileged. See 6300270.
+                AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                    public Void run() {
+                        run.run();
+                        return null;
+                    }
+                });
+            } else {
+                if (log.isLoggable(PlatformLogger.Level.FINEST)) {
+                    log.finest("On non-dispatch thread: " + currentThread);
+                }
+                keepBlockingCT.set(true);
+                synchronized (getTreeLock()) {
+                    if (afterExit.get()) return false;
+                    if (filter != null) {
+                        dispatchThread.addEventFilter(filter);
+                    }
+                    try {
+                        EventQueue eq = dispatchThread.getEventQueue();
+                        eq.postEvent(new PeerEvent(this, run, PeerEvent.PRIORITY_EVENT));
+                        if (interval > 0) {
+                            long currTime = System.currentTimeMillis();
+                            while (keepBlockingCT.get() &&
+                                    ((extCondition != null) ? extCondition.evaluate() : true) &&
+                                    (currTime + interval > System.currentTimeMillis()))
+                            {
+                                getTreeLock().wait(interval);
+                            }
+                        } else {
+                            while (keepBlockingCT.get() &&
+                                    ((extCondition != null) ? extCondition.evaluate() : true))
+                            {
+                                getTreeLock().wait();
+                            }
+                        }
+                        if (log.isLoggable(PlatformLogger.Level.FINE)) {
+                            log.fine("waitDone " + keepBlockingEDT.get() + " " + keepBlockingCT.get());
+                        }
+                    } catch (InterruptedException e) {
+                        if (log.isLoggable(PlatformLogger.Level.FINE)) {
+                            log.fine("Exception caught while waiting: " + e);
+                        }
+                    } finally {
+                        if (filter != null) {
+                            dispatchThread.removeEventFilter(filter);
                         }
                     }
-                }, interval);
-            }
-            // Dispose SequencedEvent we are dispatching on the current
-            // AppContext, to prevent us from hang - see 4531693 for details
-            SequencedEvent currentSE = KeyboardFocusManager.
-                getCurrentKeyboardFocusManager().getCurrentSequencedEvent();
-            if (currentSE != null) {
-                if (log.isLoggable(PlatformLogger.Level.FINE)) {
-                    log.fine("Dispose current SequencedEvent: " + currentSE);
                 }
-                currentSE.dispose();
-            }
-            // In case the exit() method is called before starting
-            // new event pump it will post the waking event to EDT.
-            // The event will be handled after the new event pump
-            // starts. Thus, the enter() method will not hang.
-            //
-            // Event pump should be privileged. See 6300270.
-            AccessController.doPrivileged(new PrivilegedAction<Void>() {
-                public Void run() {
-                    run.run();
-                    return null;
-                }
-            });
-        } else {
-            if (log.isLoggable(PlatformLogger.Level.FINEST)) {
-                log.finest("On non-dispatch thread: " + currentThread);
             }
-            synchronized (getTreeLock()) {
-                if (filter != null) {
-                    dispatchThread.addEventFilter(filter);
-                }
-                try {
-                    EventQueue eq = dispatchThread.getEventQueue();
-                    eq.postEvent(new PeerEvent(this, run, PeerEvent.PRIORITY_EVENT));
-                    keepBlockingCT.set(true);
-                    if (interval > 0) {
-                        long currTime = System.currentTimeMillis();
-                        while (keepBlockingCT.get() &&
-                               ((extCondition != null) ? extCondition.evaluate() : true) &&
-                               (currTime + interval > System.currentTimeMillis()))
-                        {
-                            getTreeLock().wait(interval);
-                        }
-                    } else {
-                        while (keepBlockingCT.get() &&
-                               ((extCondition != null) ? extCondition.evaluate() : true))
-                        {
-                            getTreeLock().wait();
-                        }
-                    }
-                    if (log.isLoggable(PlatformLogger.Level.FINE)) {
-                        log.fine("waitDone " + keepBlockingEDT.get() + " " + keepBlockingCT.get());
-                    }
-                } catch (InterruptedException e) {
-                    if (log.isLoggable(PlatformLogger.Level.FINE)) {
-                        log.fine("Exception caught while waiting: " + e);
-                    }
-                } finally {
-                    if (filter != null) {
-                        dispatchThread.removeEventFilter(filter);
-                    }
-                }
-                // If the waiting process has been stopped because of the
-                // time interval passed or an exception occurred, the state
-                // should be changed
-                keepBlockingEDT.set(false);
-                keepBlockingCT.set(false);
-            }
+            return true;
         }
-
-        return true;
+        finally {
+            keepBlockingEDT.set(false);
+            keepBlockingCT.set(false);
+            afterExit.set(false);
+        }
     }
 
     /**
@@ -288,7 +295,8 @@
             log.fine("exit(): blockingEDT=" + keepBlockingEDT.get() +
                      ", blockingCT=" + keepBlockingCT.get());
         }
-        if (keepBlockingEDT.compareAndSet(true, false)) {
+        afterExit.set(true);
+        if (keepBlockingEDT.getAndSet(false)) {
             wakeupEDT();
             return true;
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/EventQueue/6980209/bug6980209.java	Fri May 08 15:37:38 2015 +0300
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/* @test
+   @bug 6980209
+   @summary Make tracking SecondaryLoop.enter/exit methods easier
+   @author Semyon Sadetsky
+  */
+
+import sun.util.logging.PlatformLogger;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+
+public class bug6980209 implements ActionListener {
+    private final static PlatformLogger log =
+            PlatformLogger.getLogger("java.awt.event.WaitDispatchSupport");
+    public static final int ATTEMPTS = 100;
+    public static final int EVENTS = 5;
+
+    private static boolean runInEDT;
+    private static JFrame frame;
+    private static int disorderCounter = 0;
+    private static Boolean enterReturn;
+    private static Boolean exitReturn;
+    private static int dispatchedEvents;
+
+    public static void main(String[] args) throws Exception {
+        System.out.println(
+                "PLEASE DO NOT TOUCH KEYBOARD AND MOUSE DURING THE TEST RUN!");
+        // log.setLevel(PlatformLogger.Level.FINE);
+        // log.setLevel(PlatformLogger.Level.FINEST);
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                public void run() {
+                    frame = new JFrame();
+                    frame.setUndecorated(true);
+                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+                    setup(frame);
+                }
+            });
+            testExitBeforeEnter();
+            System.out.println("Run random test in EDT");
+            runInEDT = true;
+            testRandomly();
+            System.out.println("Run random test in another thread");
+            runInEDT = false;
+            testRandomly();
+            System.out.println("ok");
+
+        } finally {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                @Override
+                public void run() {
+                    frame.dispose();
+                }
+            });
+        }
+    }
+
+    private static void testExitBeforeEnter() throws Exception {
+        final SecondaryLoop loop =
+                Toolkit.getDefaultToolkit().getSystemEventQueue()
+                        .createSecondaryLoop();
+        loop.exit();
+        Robot robot = new Robot();
+        robot.mouseWheel(1);
+        robot.waitForIdle();
+        SwingUtilities.invokeAndWait(new Runnable() {
+            @Override
+            public void run() {
+                if(loop.enter()) {
+                    throw new RuntimeException("Wrong enter() return value");
+                }
+            }
+        });
+    }
+
+    private static void testRandomly() throws AWTException {
+        disorderCounter = 0;
+        final Robot robot = new Robot();
+        for (int i = 0; i < ATTEMPTS; i++) {
+            enterReturn = null;
+            exitReturn = null;
+            dispatchedEvents = 0;
+            synchronized (bug6980209.class) {
+                try {
+                    for (int j = 0; j < EVENTS; j++) {
+                        robot.keyPress(KeyEvent.VK_1);
+                        robot.keyRelease(KeyEvent.VK_1);
+                    }
+
+                    // trigger the button action that starts secondary loop
+                    robot.keyPress(KeyEvent.VK_SPACE);
+                    robot.keyRelease(KeyEvent.VK_SPACE);
+
+                    for (int j = 0; j < EVENTS; j++) {
+                        robot.keyPress(KeyEvent.VK_1);
+                        robot.keyRelease(KeyEvent.VK_1);
+                    }
+                    long time = System.nanoTime();
+                    // wait for enter() returns
+                    bug6980209.class.wait(1000);
+                    if (enterReturn == null) {
+                        System.out.println("wait time=" +
+                                ((System.nanoTime() - time) / 1E9) +
+                                " seconds");
+                        throw new RuntimeException(
+                                "It seems the secondary loop will never end");
+                    }
+                    if (!enterReturn) disorderCounter++;
+
+                    robot.waitForIdle();
+                    if (dispatchedEvents <
+                            2 * EVENTS) { //check that all events are dispatched
+                        throw new RuntimeException(
+                                "KeyEvent.VK_1 has been lost!");
+                    }
+
+                } catch (InterruptedException e) {
+                    throw new RuntimeException("Interrupted!");
+                }
+            }
+        }
+        if (disorderCounter == 0) {
+            System.out.println(
+                    "Zero disordered enter/exit caught. It is recommended to run scenario again");
+        } else {
+            System.out.println(
+                    "Disordered calls is " + disorderCounter + " from " +
+                            ATTEMPTS);
+        }
+    }
+
+    private static void setup(final JFrame frame) {
+        JButton jButton = new JButton("Button");
+        frame.getContentPane().add(jButton);
+        jButton.addActionListener(new bug6980209());
+        frame.pack();
+        frame.setVisible(true);
+        jButton.setFocusable(true);
+        jButton.requestFocus();
+        jButton.addKeyListener(new KeyListener() {
+            @Override
+            public void keyTyped(KeyEvent e) {
+            }
+
+            @Override
+            public void keyPressed(KeyEvent e) {
+                if (e.getKeyChar() == '1') dispatchedEvents++;
+            }
+
+            @Override
+            public void keyReleased(KeyEvent e) {
+                if (e.getKeyChar() == '1') dispatchedEvents++;
+            }
+        });
+    }
+
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        if (runInEDT) {
+            runSecondaryLoop();
+            return;
+        }
+        new Thread("Secondary loop run thread") {
+            @Override
+            public void run() {
+                runSecondaryLoop();
+            }
+        }.start();
+    }
+
+    private static void runSecondaryLoop() {
+        log.fine("\n---TEST START---");
+
+        final SecondaryLoop loop =
+                Toolkit.getDefaultToolkit().getSystemEventQueue()
+                        .createSecondaryLoop();
+
+        final Object LOCK = new Object(); //lock to start simultaneously
+        Thread exitThread = new Thread("Exit thread") {
+            @Override
+            public void run() {
+                synchronized (LOCK) {
+                    LOCK.notify();
+                }
+                Thread.yield();
+                exitReturn = loop.exit();
+                log.fine("exit() returns " + exitReturn);
+            }
+        };
+
+        synchronized (LOCK) {
+            try {
+                exitThread.start();
+                LOCK.wait();
+            } catch (InterruptedException e1) {
+                throw new RuntimeException("What?");
+            }
+        }
+
+        enterReturn = loop.enter();
+        log.fine("enter() returns " + enterReturn);
+
+        try {
+            exitThread.join();
+        } catch (InterruptedException e) {
+            throw new RuntimeException("What?");
+        }
+        synchronized (bug6980209.class) {
+            bug6980209.class.notifyAll();
+        }
+        log.fine("\n---TEST END---");
+    }
+}