7186109: Simplify lock machinery for PostEventQueue & EventQueue
authorbagiras
Thu, 13 Sep 2012 19:53:13 +0400
changeset 13775 3ab536cab1d1
parent 13774 62bde3c50b97
child 13776 e36d4f20eda7
7186109: Simplify lock machinery for PostEventQueue & EventQueue Reviewed-by: art, anthony, dholmes
jdk/src/macosx/classes/sun/lwawt/macosx/LWCToolkit.java
jdk/src/share/classes/java/awt/EventQueue.java
jdk/src/share/classes/sun/awt/SunToolkit.java
jdk/test/java/awt/EventQueue/PostEventOrderingTest/PostEventOrderingTest.java
--- a/jdk/src/macosx/classes/sun/lwawt/macosx/LWCToolkit.java	Wed Sep 12 21:16:39 2012 +0400
+++ b/jdk/src/macosx/classes/sun/lwawt/macosx/LWCToolkit.java	Thu Sep 13 19:53:13 2012 +0400
@@ -536,7 +536,7 @@
             SunToolkit.postEvent(appContext, invocationEvent);
 
             // 3746956 - flush events from PostEventQueue to prevent them from getting stuck and causing a deadlock
-            sun.awt.SunToolkitSubclass.flushPendingEvents(appContext);
+            SunToolkit.flushPendingEvents(appContext);
         } else {
             // This should be the equivalent to EventQueue.invokeAndWait
             ((LWCToolkit)Toolkit.getDefaultToolkit()).getSystemEventQueueForInvokeAndWait().postEvent(invocationEvent);
@@ -561,7 +561,7 @@
             SunToolkit.postEvent(appContext, invocationEvent);
 
             // 3746956 - flush events from PostEventQueue to prevent them from getting stuck and causing a deadlock
-            sun.awt.SunToolkitSubclass.flushPendingEvents(appContext);
+            SunToolkit.flushPendingEvents(appContext);
         } else {
             // This should be the equivalent to EventQueue.invokeAndWait
             ((LWCToolkit)Toolkit.getDefaultToolkit()).getSystemEventQueueForInvokeAndWait().postEvent(invocationEvent);
--- a/jdk/src/share/classes/java/awt/EventQueue.java	Wed Sep 12 21:16:39 2012 +0400
+++ b/jdk/src/share/classes/java/awt/EventQueue.java	Thu Sep 13 19:53:13 2012 +0400
@@ -1047,6 +1047,10 @@
 
     final boolean detachDispatchThread(EventDispatchThread edt, boolean forceDetach) {
         /*
+         * Minimize discard possibility for non-posted events
+         */
+        SunToolkit.flushPendingEvents();
+        /*
          * This synchronized block is to secure that the event dispatch
          * thread won't die in the middle of posting a new event to the
          * associated event queue. It is important because we notify
@@ -1060,11 +1064,8 @@
                 /*
                  * Don't detach the thread if any events are pending. Not
                  * sure if it's a possible scenario, though.
-                 *
-                 * Fix for 4648733. Check both the associated java event
-                 * queue and the PostEventQueue.
                  */
-                if (!forceDetach && (peekEvent() != null) || !SunToolkit.isPostEventQueueEmpty()) {
+                if (!forceDetach && (peekEvent() != null)) {
                     return false;
                 }
                 dispatchThread = null;
--- a/jdk/src/share/classes/sun/awt/SunToolkit.java	Wed Sep 12 21:16:39 2012 +0400
+++ b/jdk/src/share/classes/sun/awt/SunToolkit.java	Thu Sep 13 19:53:13 2012 +0400
@@ -506,40 +506,25 @@
         postEvent(targetToAppContext(e.getSource()), pe);
     }
 
-    protected static final Lock flushLock = new ReentrantLock();
-    private static boolean isFlushingPendingEvents = false;
-
     /*
      * Flush any pending events which haven't been posted to the AWT
      * EventQueue yet.
      */
     public static void flushPendingEvents()  {
-        flushLock.lock();
-        try {
-            // Don't call flushPendingEvents() recursively
-            if (!isFlushingPendingEvents) {
-                isFlushingPendingEvents = true;
-                AppContext appContext = AppContext.getAppContext();
-                PostEventQueue postEventQueue =
-                    (PostEventQueue)appContext.get(POST_EVENT_QUEUE_KEY);
-                if (postEventQueue != null) {
-                    postEventQueue.flush();
-                }
-            }
-        } finally {
-            isFlushingPendingEvents = false;
-            flushLock.unlock();
-        }
+        AppContext appContext = AppContext.getAppContext();
+        flushPendingEvents(appContext);
     }
 
-    public static boolean isPostEventQueueEmpty()  {
-        AppContext appContext = AppContext.getAppContext();
+    /*
+     * Flush the PostEventQueue for the right AppContext.
+     * The default flushPendingEvents only flushes the thread-local context,
+     * which is not always correct, c.f. 3746956
+     */
+    public static void flushPendingEvents(AppContext appContext) {
         PostEventQueue postEventQueue =
-            (PostEventQueue)appContext.get(POST_EVENT_QUEUE_KEY);
+                (PostEventQueue)appContext.get(POST_EVENT_QUEUE_KEY);
         if (postEventQueue != null) {
-            return postEventQueue.noEvents();
-        } else {
-            return true;
+            postEventQueue.flush();
         }
     }
 
@@ -2045,17 +2030,12 @@
     private EventQueueItem queueTail = null;
     private final EventQueue eventQueue;
 
-    // For the case when queue is cleared but events are not posted
-    private volatile boolean isFlushing = false;
+    private Thread flushThread = null;
 
     PostEventQueue(EventQueue eq) {
         eventQueue = eq;
     }
 
-    public synchronized boolean noEvents() {
-        return queueHead == null && !isFlushing;
-    }
-
     /*
      * Continually post pending AWTEvents to the Java EventQueue. The method
      * is synchronized to ensure the flush is completed before a new event
@@ -2066,20 +2046,48 @@
      * potentially lead to deadlock
      */
     public void flush() {
-        EventQueueItem tempQueue;
-        synchronized (this) {
-            tempQueue = queueHead;
-            queueHead = queueTail = null;
-            isFlushing = true;
-        }
+
+        Thread newThread = Thread.currentThread();
+
         try {
-            while (tempQueue != null) {
-                eventQueue.postEvent(tempQueue.event);
-                tempQueue = tempQueue.next;
+            EventQueueItem tempQueue;
+            synchronized (this) {
+                // Avoid method recursion
+                if (newThread == flushThread) {
+                    return;
+                }
+                // Wait for other threads' flushing
+                while (flushThread != null) {
+                    wait();
+                }
+                // Skip everything if queue is empty
+                if (queueHead == null) {
+                    return;
+                }
+                // Remember flushing thread
+                flushThread = newThread;
+
+                tempQueue = queueHead;
+                queueHead = queueTail = null;
+            }
+            try {
+                while (tempQueue != null) {
+                    eventQueue.postEvent(tempQueue.event);
+                    tempQueue = tempQueue.next;
+                }
+            }
+            finally {
+                // Only the flushing thread can get here
+                synchronized (this) {
+                    // Forget flushing thread, inform other pending threads
+                    flushThread = null;
+                    notifyAll();
+                }
             }
         }
-        finally {
-            isFlushing = false;
+        catch (InterruptedException e) {
+            // Couldn't allow exception go up, so at least recover the flag
+            newThread.interrupt();
         }
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/EventQueue/PostEventOrderingTest/PostEventOrderingTest.java	Thu Sep 13 19:53:13 2012 +0400
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2012, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 PostEventOrderingTest.java
+ * @bug 4171596 6699589
+ * @summary Checks that the posting of events between the PostEventQueue
+ * @summary and the EventQueue maintains proper ordering.
+ * @run main PostEventOrderingTest
+ * @author fredx
+ */
+
+import java.awt.*;
+import java.awt.event.*;
+import sun.awt.AppContext;
+import sun.awt.SunToolkit;
+
+public class PostEventOrderingTest {
+    static boolean testPassed = true;
+
+    public static void main(String[] args) throws Throwable {
+        EventQueue q = Toolkit.getDefaultToolkit().getSystemEventQueue();
+        for (int i = 0; i < 100; i++) {
+            for (int j = 0; j < 100; j++) {
+                q.postEvent(new PostActionEvent());
+                for (int k = 0; k < 10; k++) {
+                    SunToolkit.postEvent(AppContext.getAppContext(), new PostActionEvent());
+                }
+            }
+            for (int k = 0; k < 100; k++) {
+                SunToolkit.postEvent(AppContext.getAppContext(), new PostActionEvent());
+            }
+        }
+
+        for (;;) {
+            Thread.currentThread().sleep(100);
+            if (q.peekEvent() == null) {
+                Thread.currentThread().sleep(100);
+                if (q.peekEvent() == null)
+                    break;
+            }
+        }
+
+        if (!testPassed) {
+            throw new Exception("PostEventOrderingTest FAILED -- events dispatched out of order.");
+        } else {
+            System.out.println("PostEventOrderingTest passed!");
+        }
+    }
+}
+
+class PostActionEvent extends ActionEvent implements ActiveEvent {
+    static int counter = 0;
+    static int mostRecent = -1;
+
+    int myval;
+
+    public PostActionEvent() {
+        super("", ACTION_PERFORMED, "" + counter);
+        myval = counter++;
+    }
+
+    public void dispatch() {
+        //System.out.println("myval = "+myval+", mostRecent = "+mostRecent+", diff = "+(myval-mostRecent)+".");
+        if ((myval - mostRecent) != 1)
+            PostEventOrderingTest.testPassed = false;
+        mostRecent = myval;
+    }
+}