changeset 1898:50321e4d46eb

6852111: Unhandled 'spurious wakeup' in java.awt.EventQueue.invokeAndWait() Summary: Introduced InvocationEvent.isDispatched method Reviewed-by: art, anthony
author dcherepanov
date Wed, 11 Nov 2009 17:46:58 +0300
parents 4ed8bce48963
children 7dd452521ab3
files src/share/classes/java/awt/EventQueue.java src/share/classes/java/awt/event/InvocationEvent.java src/share/classes/sun/awt/im/InputMethodManager.java test/java/awt/event/InvocationEvent/InvocationEventTest.java
diffstat 4 files changed, 149 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/awt/EventQueue.java	Fri Oct 23 14:52:55 2009 +0400
+++ b/src/share/classes/java/awt/EventQueue.java	Wed Nov 11 17:46:58 2009 +0300
@@ -1027,7 +1027,9 @@
 
         synchronized (lock) {
             Toolkit.getEventQueue().postEvent(event);
-            lock.wait();
+            while (!event.isDispatched()) {
+                lock.wait();
+            }
         }
 
         Throwable eventThrowable = event.getThrowable();
--- a/src/share/classes/java/awt/event/InvocationEvent.java	Fri Oct 23 14:52:55 2009 +0400
+++ b/src/share/classes/java/awt/event/InvocationEvent.java	Wed Nov 11 17:46:58 2009 +0300
@@ -78,11 +78,22 @@
 
     /**
      * The (potentially null) Object whose notifyAll() method will be called
-     * immediately after the Runnable.run() method returns.
+     * immediately after the Runnable.run() method has returned or thrown an exception.
+     *
+     * @see #isDispatched
      */
     protected Object notifier;
 
     /**
+     * Indicates whether the <code>run()</code> method of the <code>runnable</code>
+     * was executed or not.
+     *
+     * @see #isDispatched
+     * @since 1.7
+     */
+    private volatile boolean dispatched = false;
+
+    /**
      * Set to true if dispatch() catches Throwable and stores it in the
      * exception instance variable. If false, Throwables are propagated up
      * to the EventDispatchThread's dispatch loop.
@@ -144,7 +155,7 @@
      * source which will execute the runnable's <code>run</code>
      * method when dispatched.  If notifier is non-<code>null</code>,
      * <code>notifyAll()</code> will be called on it
-     * immediately after <code>run</code> returns.
+     * immediately after <code>run</code> has returned or thrown an exception.
      * <p>An invocation of the form <tt>InvocationEvent(source,
      * runnable, notifier, catchThrowables)</tt>
      * behaves in exactly the same way as the invocation of
@@ -159,7 +170,8 @@
      *                          executed
      * @param notifier          The {@code Object} whose <code>notifyAll</code>
      *                          method will be called after
-     *                          <code>Runnable.run</code> has returned
+     *                          <code>Runnable.run</code> has returned or
+     *                          thrown an exception
      * @param catchThrowables   Specifies whether <code>dispatch</code>
      *                          should catch Throwable when executing
      *                          the <code>Runnable</code>'s <code>run</code>
@@ -180,8 +192,8 @@
      * Constructs an <code>InvocationEvent</code> with the specified
      * source and ID which will execute the runnable's <code>run</code>
      * method when dispatched.  If notifier is non-<code>null</code>,
-     * <code>notifyAll</code> will be called on it
-     * immediately after <code>run</code> returns.
+     * <code>notifyAll</code> will be called on it immediately after
+     * <code>run</code> has returned or thrown an exception.
      * <p>This method throws an
      * <code>IllegalArgumentException</code> if <code>source</code>
      * is <code>null</code>.
@@ -195,7 +207,8 @@
      *                          <code>run</code> method will be executed
      * @param notifier          The <code>Object</code> whose <code>notifyAll</code>
      *                          method will be called after
-     *                          <code>Runnable.run</code> has returned
+     *                          <code>Runnable.run</code> has returned or
+     *                          thrown an exception
      * @param catchThrowables   Specifies whether <code>dispatch</code>
      *                          should catch Throwable when executing the
      *                          <code>Runnable</code>'s <code>run</code>
@@ -217,27 +230,33 @@
 
     /**
      * Executes the Runnable's <code>run()</code> method and notifies the
-     * notifier (if any) when <code>run()</code> returns.
+     * notifier (if any) when <code>run()</code> has returned or thrown an exception.
+     *
+     * @see #isDispatched
      */
     public void dispatch() {
-        if (catchExceptions) {
-            try {
+        try {
+            if (catchExceptions) {
+                try {
+                    runnable.run();
+                }
+                catch (Throwable t) {
+                    if (t instanceof Exception) {
+                        exception = (Exception) t;
+                    }
+                    throwable = t;
+                }
+            }
+            else {
                 runnable.run();
             }
-            catch (Throwable t) {
-                if (t instanceof Exception) {
-                    exception = (Exception) t;
+        } finally {
+            dispatched = true;
+
+            if (notifier != null) {
+                synchronized (notifier) {
+                    notifier.notifyAll();
                 }
-                throwable = t;
-            }
-        }
-        else {
-            runnable.run();
-        }
-
-        if (notifier != null) {
-            synchronized (notifier) {
-                notifier.notifyAll();
             }
         }
     }
@@ -278,6 +297,40 @@
     }
 
     /**
+     * Returns {@code true} if the event is dispatched or any exception is
+     * thrown while dispatching, {@code false} otherwise. The method should
+     * be called by a waiting thread that calls the {@code notifier.wait()} method.
+     * Since spurious wakeups are possible (as explained in {@link Object#wait()}),
+     * this method should be used in a waiting loop to ensure that the event
+     * got dispatched:
+     * <pre>
+     *     while (!event.isDispatched()) {
+     *         notifier.wait();
+     *     }
+     * </pre>
+     * If the waiting thread wakes up without dispatching the event,
+     * the {@code isDispatched()} method returns {@code false}, and
+     * the {@code while} loop executes once more, thus, causing
+     * the awakened thread to revert to the waiting mode.
+     * <p>
+     * If the {@code notifier.notifyAll()} happens before the waiting thread
+     * enters the {@code notifier.wait()} method, the {@code while} loop ensures
+     * that the waiting thread will not enter the {@code notifier.wait()} method.
+     * Otherwise, there is no guarantee that the waiting thread will ever be woken
+     * from the wait.
+     *
+     * @return {@code true} if the event has been dispatched, or any exception
+     * has been thrown while dispatching, {@code false} otherwise
+     * @see #dispatch
+     * @see #notifier
+     * @see #catchExceptions
+     * @since 1.7
+     */
+    public boolean isDispatched() {
+        return dispatched;
+    }
+
+    /**
      * Returns a parameter string identifying this event.
      * This method is useful for event-logging and for debugging.
      *
--- a/src/share/classes/sun/awt/im/InputMethodManager.java	Fri Oct 23 14:52:55 2009 +0400
+++ b/src/share/classes/sun/awt/im/InputMethodManager.java	Wed Nov 11 17:46:58 2009 +0300
@@ -358,7 +358,9 @@
         AppContext requesterAppContext = SunToolkit.targetToAppContext(requester);
         synchronized (lock) {
             SunToolkit.postEvent(requesterAppContext, event);
-            lock.wait();
+            while (!event.isDispatched()) {
+                lock.wait();
+            }
         }
 
         Throwable eventThrowable = event.getThrowable();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/awt/event/InvocationEvent/InvocationEventTest.java	Wed Nov 11 17:46:58 2009 +0300
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+  @test
+  @bug 6852111
+  @summary Unhandled 'spurious wakeup' in java.awt.EventQueue.invokeAndWait()
+  @author dmitry.cherepanov@sun.com: area=awt.event
+  @run main InvocationEventTest
+*/
+
+/**
+ * InvocationEventTest.java
+ *
+ * summary: Tests new isDispatched method of the InvocationEvent class
+ */
+
+import java.awt.*;
+import java.awt.event.*;
+
+public class InvocationEventTest
+{
+    public static void main(String []s) throws Exception
+    {
+        Toolkit tk = Toolkit.getDefaultToolkit();
+        Runnable runnable = new Runnable() {
+            public void run() {
+            }
+        };
+        Object lock = new Object();
+        InvocationEvent event = new InvocationEvent(tk, runnable, lock, true);
+
+        if (event.isDispatched()) {
+            throw new RuntimeException(" Initially, the event shouldn't be dispatched ");
+        }
+
+        synchronized(lock) {
+            tk.getSystemEventQueue().postEvent(event);
+            while(!event.isDispatched()) {
+                lock.wait();
+            }
+        }
+
+        if(!event.isDispatched()) {
+            throw new RuntimeException(" Finally, the event should be dispatched ");
+        }
+    }
+}