6980209: Make tracking SecondaryLoop.enter/exit methods easier
Reviewed-by: serb, ant
--- 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---");
+ }
+}