changeset 2695:7e26538596be

6949936: Provide API for running nested events loops, similar to what modal dialogs do Reviewed-by: ant, anthony
author art
date Tue, 24 Aug 2010 12:54:46 +0400
parents 3b0abcb51280
children d3fdf9e7e9c2
files src/share/classes/java/awt/Dialog.java src/share/classes/java/awt/EventDispatchThread.java src/share/classes/java/awt/EventQueue.java src/share/classes/java/awt/SecondaryLoop.java src/share/classes/java/awt/WaitDispatchSupport.java test/java/awt/EventQueue/SecondaryLoopTest/SecondaryLoopTest.java
diffstat 6 files changed, 629 insertions(+), 98 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/awt/Dialog.java	Mon Aug 09 16:02:19 2010 -0700
+++ b/src/share/classes/java/awt/Dialog.java	Tue Aug 24 12:54:46 2010 +0400
@@ -277,10 +277,8 @@
      */
     String title;
 
-    private transient volatile boolean keepBlockingEDT = false;
-    private transient volatile boolean keepBlockingCT = false;
-
     private transient ModalEventFilter modalFilter;
+    private transient volatile SecondaryLoop secondaryLoop;
 
     /*
      * Indicates that this dialog is being hidden. This flag is set to true at
@@ -1005,12 +1003,6 @@
         super.setVisible(b);
     }
 
-    /**
-    * Stores the app context on which event dispatch thread the dialog
-    * is being shown. Initialized in show(), used in hideAndDisposeHandler()
-    */
-    transient private AppContext showAppContext;
-
    /**
      * Makes the {@code Dialog} visible. If the dialog and/or its owner
      * are not yet displayable, both are made displayable.  The
@@ -1037,39 +1029,18 @@
         if (!isModal()) {
             conditionalShow(null, null);
         } else {
-            // Set this variable before calling conditionalShow(). That
-            // way, if the Dialog is hidden right after being shown, we
-            // won't mistakenly block this thread.
-            keepBlockingEDT = true;
-            keepBlockingCT = true;
-
-            // Store the app context on which this dialog is being shown.
-            // Event dispatch thread of this app context will be sleeping until
-            // we wake it by any event from hideAndDisposeHandler().
-            showAppContext = AppContext.getAppContext();
+            AppContext showAppContext = AppContext.getAppContext();
 
             AtomicLong time = new AtomicLong();
             Component predictedFocusOwner = null;
             try {
                 predictedFocusOwner = getMostRecentFocusOwner();
                 if (conditionalShow(predictedFocusOwner, time)) {
-                    // We have two mechanisms for blocking: 1. If we're on the
-                    // EventDispatchThread, start a new event pump. 2. If we're
-                    // on any other thread, call wait() on the treelock.
-
                     modalFilter = ModalEventFilter.createFilterForDialog(this);
-
-                    final Runnable pumpEventsForFilter = new Runnable() {
-                        public void run() {
-                            EventDispatchThread dispatchThread =
-                                (EventDispatchThread)Thread.currentThread();
-                            dispatchThread.pumpEventsForFilter(new Conditional() {
-                                public boolean evaluate() {
-                                    synchronized (getTreeLock()) {
-                                        return keepBlockingEDT && windowClosingException == null;
-                                    }
-                                }
-                            }, modalFilter);
+                    Conditional cond = new Conditional() {
+                        @Override
+                        public boolean evaluate() {
+                            return windowClosingException == null;
                         }
                     };
 
@@ -1096,44 +1067,10 @@
 
                     modalityPushed();
                     try {
-                        if (EventQueue.isDispatchThread()) {
-                            /*
-                             * dispose SequencedEvent we are dispatching on current
-                             * AppContext, to prevent us from hang.
-                             *
-                             */
-                            // BugId 4531693 (son@sparc.spb.su)
-                            SequencedEvent currentSequencedEvent = KeyboardFocusManager.
-                                getCurrentKeyboardFocusManager().getCurrentSequencedEvent();
-                            if (currentSequencedEvent != null) {
-                                currentSequencedEvent.dispose();
-                            }
-
-                            /*
-                             * Event processing is done inside doPrivileged block so that
-                             * it wouldn't matter even if user code is on the stack
-                             * Fix for BugId 6300270
-                             */
-
-                             AccessController.doPrivileged(new PrivilegedAction() {
-                                     public Object run() {
-                                        pumpEventsForFilter.run();
-                                        return null;
-                                     }
-                             });
-                        } else {
-                            synchronized (getTreeLock()) {
-                                Toolkit.getEventQueue().postEvent(new PeerEvent(this,
-                                                                                pumpEventsForFilter,
-                                                                                PeerEvent.PRIORITY_EVENT));
-                                while (keepBlockingCT && windowClosingException == null) {
-                                    try {
-                                        getTreeLock().wait();
-                                    } catch (InterruptedException e) {
-                                        break;
-                                    }
-                                }
-                            }
+                        EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
+                        secondaryLoop = eventQueue.createSecondaryLoop(cond, modalFilter, 5000);
+                        if (!secondaryLoop.enter()) {
+                            secondaryLoop = null;
                         }
                     } finally {
                         modalityPopped();
@@ -1194,18 +1131,11 @@
             windowClosingException = null;
         }
     }
-    final class WakingRunnable implements Runnable {
-        public void run() {
-            synchronized (getTreeLock()) {
-                keepBlockingCT = false;
-                getTreeLock().notifyAll();
-            }
-        }
-    }
+
     private void hideAndDisposePreHandler() {
         isInHide = true;
         synchronized (getTreeLock()) {
-            if (keepBlockingEDT) {
+            if (secondaryLoop != null) {
                 modalHide();
                 // dialog can be shown and then disposed before its
                 // modal filter is created
@@ -1217,20 +1147,9 @@
         }
     }
     private void hideAndDisposeHandler() {
-        synchronized (getTreeLock()) {
-            if (keepBlockingEDT) {
-                keepBlockingEDT = false;
-                PeerEvent wakingEvent = new PeerEvent(getToolkit(), new WakingRunnable(), PeerEvent.PRIORITY_EVENT);
-                AppContext curAppContext = AppContext.getAppContext();
-                if (showAppContext != curAppContext) {
-                    // Wake up event dispatch thread on which the dialog was
-                    // initially shown
-                    SunToolkit.postEvent(showAppContext, wakingEvent);
-                    showAppContext = null;
-                } else {
-                    Toolkit.getEventQueue().postEvent(wakingEvent);
-                }
-            }
+        if (secondaryLoop != null) {
+            secondaryLoop.exit();
+            secondaryLoop = null;
         }
         isInHide = false;
     }
--- a/src/share/classes/java/awt/EventDispatchThread.java	Mon Aug 09 16:02:19 2010 -0700
+++ b/src/share/classes/java/awt/EventDispatchThread.java	Tue Aug 24 12:54:46 2010 +0400
@@ -113,8 +113,7 @@
         pumpEventsForHierarchy(id, cond, null);
     }
 
-    void pumpEventsForHierarchy(int id, Conditional cond, Component modalComponent)
-    {
+    void pumpEventsForHierarchy(int id, Conditional cond, Component modalComponent) {
         pumpEventsForFilter(id, cond, new HierarchyEventFilter(modalComponent));
     }
 
@@ -124,6 +123,7 @@
 
     void pumpEventsForFilter(int id, Conditional cond, EventFilter filter) {
         addEventFilter(filter);
+        doDispatch = true;
         while (doDispatch && cond.evaluate()) {
             if (isInterrupted() || !pumpOneEventForFilters(id)) {
                 doDispatch = false;
@@ -133,6 +133,7 @@
     }
 
     void addEventFilter(EventFilter filter) {
+        eventLog.finest("adding the event filter: " + filter);
         synchronized (eventFilters) {
             if (!eventFilters.contains(filter)) {
                 if (filter instanceof ModalEventFilter) {
@@ -156,6 +157,7 @@
     }
 
     void removeEventFilter(EventFilter filter) {
+        eventLog.finest("removing the event filter: " + filter);
         synchronized (eventFilters) {
             eventFilters.remove(filter);
         }
--- a/src/share/classes/java/awt/EventQueue.java	Mon Aug 09 16:02:19 2010 -0700
+++ b/src/share/classes/java/awt/EventQueue.java	Tue Aug 24 12:54:46 2010 +0400
@@ -884,6 +884,41 @@
     }
 
     /**
+     * Creates a new {@code secondary loop} associated with this
+     * event queue. Use the {@link SecondaryLoop#enter} and
+     * {@link SecondaryLoop#exit} methods to start and stop the
+     * event loop and dispatch the events from this queue.
+     *
+     * @return secondaryLoop A new secondary loop object, which can
+     *                       be used to launch a new nested event
+     *                       loop and dispatch events from this queue
+     *
+     * @see SecondaryLoop#enter
+     * @see SecondaryLoop#exit
+     *
+     * @since 1.7
+     */
+    public SecondaryLoop createSecondaryLoop() {
+        return createSecondaryLoop(null, null, 0);
+    }
+
+    SecondaryLoop createSecondaryLoop(Conditional cond, EventFilter filter, long interval) {
+        pushPopLock.lock();
+        try {
+            if (nextQueue != null) {
+                // Forward the request to the top of EventQueue stack
+                return nextQueue.createSecondaryLoop(cond, filter, interval);
+            }
+            if (dispatchThread == null) {
+                initDispatchThread();
+            }
+            return new WaitDispatchSupport(dispatchThread, cond, filter, interval);
+        } finally {
+            pushPopLock.unlock();
+        }
+    }
+
+    /**
      * Returns true if the calling thread is
      * {@link Toolkit#getSystemEventQueue the current AWT EventQueue}'s
      * dispatch thread. Use this method to ensure that a particular
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/java/awt/SecondaryLoop.java	Tue Aug 24 12:54:46 2010 +0400
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2010, 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.
+ */
+
+package java.awt;
+
+/**
+ * A helper interface to run the nested event loop.
+ * <p>
+ * Objects that implement this interface are created with the
+ * {@link EventQueue#createSecondaryLoop} method. The interface
+ * provides two methods, {@link enter} and {@link exit},
+ * which can be used to start and stop the event loop.
+ * <p>
+ * When the {@link enter} method is called, the current
+ * thread is blocked until the loop is terminated by the
+ * {@link exit} method. Also, a new event loop is started
+ * on the event dispatch thread, which may or may not be
+ * the current thread. The loop can be terminated on any
+ * thread by calling its {@link exit} method. After the
+ * loop is terminated, the {@code SecondaryLoop} object can
+ * be reused to run a new nested event loop.
+ * <p>
+ * A typical use case of applying this interface is AWT
+ * and Swing modal dialogs. When a modal dialog is shown on
+ * the event dispatch thread, it enters a new secondary loop.
+ * Later, when the dialog is hidden or disposed, it exits
+ * the loop, and the thread continues its execution.
+ * <p>
+ * The following example illustrates a simple use case of
+ * secondary loops:
+ *
+ * <pre>
+ *   SecondaryLoop loop;
+ *
+ *   JButton jButton = new JButton("Button");
+ *   jButton.addActionListener(new ActionListener() {
+ *       {@code @Override}
+ *       public void actionPerformed(ActionEvent e) {
+ *           Toolkit tk = Toolkit.getDefaultToolkit();
+ *           EventQueue eq = tk.getSystemEventQueue();
+ *           loop = eq.createSecondaryLoop();
+ *
+ *           // Spawn a new thread to do the work
+ *           Thread worker = new WorkerThread();
+ *           worker.start();
+ *
+ *           // Enter the loop to block the current event
+ *           // handler, but leave UI responsive
+ *           if (!loop.enter()) {
+ *               // Report an error
+ *           }
+ *       }
+ *   });
+ *
+ *   class WorkerThread extends Thread {
+ *       {@code @Override}
+ *       public void run() {
+ *           // Perform calculations
+ *           doSomethingUseful();
+ *
+ *           // Exit the loop
+ *           loop.exit();
+ *       }
+ *   }
+ * </pre>
+ *
+ * @see Dialog#show
+ * @see EventQueue#createSecondaryLoop
+ * @see Toolkit#getSystemEventQueue
+ *
+ * @author Anton Tarasov, Artem Ananiev
+ *
+ * @since 1.7
+ */
+public interface SecondaryLoop {
+
+    /**
+     * Blocks the execution of the current thread and enters a new
+     * secondary event loop on the event dispatch thread.
+     * <p>
+     * This method can be called by any thread including the event
+     * dispatch thread. This thread will be blocked until the {@link
+     * exit} method is called or the loop is terminated. A new
+     * secondary loop will be created on the event dispatch thread
+     * for dispatching events in either case.
+     * <p>
+     * This method can only start one new event loop at a time per
+     * object. If a secondary event loop has already been started
+     * by this object and is currently still running, this method
+     * returns {@code false} to indicate that it was not successful
+     * in starting a new event loop. Otherwise, this method blocks
+     * the calling thread and later returns {@code true} when the
+     * new event loop is terminated. At such time, this object can
+     * again be used to start another new event loop.
+     *
+     * @return {@code true} after termination of the secondary loop,
+     *         if the secondary loop was started by this call,
+     *         {@code false} otherwise
+     */
+    public boolean enter();
+
+    /**
+     * Unblocks the execution of the thread blocked by the {@link
+     * enter} method and exits the secondary loop.
+     * <p>
+     * This method resumes the thread that called the {@link enter}
+     * method and exits the secondary loop that was created when
+     * the {@link enter} method was invoked.
+     * <p>
+     * Note that if any other secondary loop is started while this
+     * loop is running, the blocked thread will not resume execution
+     * until the nested loop is terminated.
+     * <p>
+     * If this secondary loop has not been started with the {@link
+     * enter} method, or this secondary loop has already finished
+     * with the {@link exit} method, this method returns {@code
+     * false}, otherwise {@code true} is returned.
+     *
+     * @return {@code true} if this loop was previously started and
+     *         has not yet been finished with the {@link exit} method,
+     *         {@code false} otherwise
+     */
+    public boolean exit();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/share/classes/java/awt/WaitDispatchSupport.java	Tue Aug 24 12:54:46 2010 +0400
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2010, 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.
+ */
+
+package java.awt;
+
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import java.security.PrivilegedAction;
+import java.security.AccessController;
+
+import sun.awt.PeerEvent;
+
+import sun.util.logging.PlatformLogger;
+
+/**
+ * This utility class is used to suspend execution on a thread
+ * while still allowing {@code EventDispatchThread} to dispatch events.
+ * The API methods of the class are thread-safe.
+ *
+ * @author Anton Tarasov, Artem Ananiev
+ *
+ * @since 1.7
+ */
+class WaitDispatchSupport implements SecondaryLoop {
+
+    private final static PlatformLogger log =
+        PlatformLogger.getLogger("java.awt.event.WaitDispatchSupport");
+
+    private EventDispatchThread dispatchThread;
+    private EventFilter filter;
+
+    private volatile Conditional extCondition;
+    private volatile Conditional condition;
+
+    private long interval;
+    // Use a shared daemon timer to serve all the WaitDispatchSupports
+    private static Timer timer;
+    // When this WDS expires, we cancel the timer task leaving the
+    // shared timer up and running
+    private TimerTask timerTask;
+
+    private AtomicBoolean keepBlockingEDT = new AtomicBoolean(false);
+    private AtomicBoolean keepBlockingCT = new AtomicBoolean(false);
+
+    private static synchronized void initializeTimer() {
+        if (timer == null) {
+            timer = new Timer("AWT-WaitDispatchSupport-Timer", true);
+        }
+    }
+
+    /**
+     * Creates a {@code WaitDispatchSupport} instance to
+     * serve the given event dispatch thread.
+     *
+     * @param dispatchThread An event dispatch thread that
+     *        should not stop dispatching events while waiting
+     *
+     * @since 1.7
+     */
+    public WaitDispatchSupport(EventDispatchThread dispatchThread) {
+        this(dispatchThread, null);
+    }
+
+    /**
+     * Creates a {@code WaitDispatchSupport} instance to
+     * serve the given event dispatch thread.
+     *
+     * @param dispatchThread An event dispatch thread that
+     *        should not stop dispatching events while waiting
+     * @param extCondition A conditional object used to determine
+     *        if the loop should be terminated
+     *
+     * @since 1.7
+     */
+    public WaitDispatchSupport(EventDispatchThread dispatchThread,
+                               Conditional extCond)
+    {
+        if (dispatchThread == null) {
+            throw new IllegalArgumentException("The dispatchThread can not be null");
+        }
+
+        this.dispatchThread = dispatchThread;
+        this.extCondition = extCond;
+        this.condition = new Conditional() {
+            @Override
+            public boolean evaluate() {
+                if (log.isLoggable(PlatformLogger.FINEST)) {
+                    log.finest("evaluate(): blockingEDT=" + keepBlockingEDT.get() +
+                               ", blockingCT=" + keepBlockingCT.get());
+                }
+                boolean extEvaluate =
+                    (extCondition != null) ? extCondition.evaluate() : true;
+                if (!keepBlockingEDT.get() || !extEvaluate) {
+                    if (timerTask != null) {
+                        timerTask.cancel();
+                        timerTask = null;
+                    }
+                    return false;
+                }
+                return true;
+            }
+        };
+    }
+
+    /**
+     * Creates a {@code WaitDispatchSupport} instance to
+     * serve the given event dispatch thread.
+     * <p>
+     * The {@link EventFilter} is set on the {@code dispatchThread}
+     * while waiting. The filter is removed on completion of the
+     * waiting process.
+     * <p>
+     *
+     *
+     * @param dispatchThread An event dispatch thread that
+     *        should not stop dispatching events while waiting
+     * @param filter {@code EventFilter} to be set
+     * @param interval A time interval to wait for. Note that
+     *        when the waiting process takes place on EDT
+     *        there is no guarantee to stop it in the given time
+     *
+     * @since 1.7
+     */
+    public WaitDispatchSupport(EventDispatchThread dispatchThread,
+                               Conditional extCondition,
+                               EventFilter filter, long interval)
+    {
+        this(dispatchThread, extCondition);
+        this.filter = filter;
+        if (interval < 0) {
+            throw new IllegalArgumentException("The interval value must be >= 0");
+        }
+        this.interval = interval;
+        if (interval != 0) {
+            initializeTimer();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    @Override
+    public boolean enter() {
+        log.fine("enter(): blockingEDT=" + keepBlockingEDT.get() +
+                 ", blockingCT=" + keepBlockingCT.get());
+
+        if (!keepBlockingEDT.compareAndSet(false, true)) {
+            log.fine("The secondary loop is already running, 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);
+                }
+            }
+        };
+
+        // 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) {
+            log.finest("On dispatch thread: " + dispatchThread);
+            if (interval != 0) {
+                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 the current
+            // AppContext, to prevent us from hang - see 4531693 for details
+            SequencedEvent currentSE = KeyboardFocusManager.
+                getCurrentKeyboardFocusManager().getCurrentSequencedEvent();
+            if (currentSE != null) {
+                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 the new event pump
+            // starts. Thus, the enter() method will not hang.
+            //
+            // Event pump should be privileged. See 6300270.
+            AccessController.doPrivileged(new PrivilegedAction() {
+                public Object run() {
+                    run.run();
+                    return null;
+                }
+            });
+        } else {
+            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();
+                        }
+                    }
+                    log.fine("waitDone " + keepBlockingEDT.get() + " " + keepBlockingCT.get());
+                } catch (InterruptedException e) {
+                    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;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public boolean exit() {
+        log.fine("exit(): blockingEDT=" + keepBlockingEDT.get() +
+                 ", blockingCT=" + keepBlockingCT.get());
+        if (keepBlockingEDT.compareAndSet(true, false)) {
+            wakeupEDT();
+            return true;
+        }
+        return false;
+    }
+
+    private final static Object getTreeLock() {
+        return Component.LOCK;
+    }
+
+    private final Runnable wakingRunnable = new Runnable() {
+        public void run() {
+            log.fine("Wake up EDT");
+            synchronized (getTreeLock()) {
+                keepBlockingCT.set(false);
+                getTreeLock().notifyAll();
+            }
+            log.fine("Wake up EDT done");
+        }
+    };
+
+    private void wakeupEDT() {
+        log.finest("wakeupEDT(): EDT == " + dispatchThread);
+        EventQueue eq = dispatchThread.getEventQueue();
+        eq.postEvent(new PeerEvent(this, wakingRunnable, PeerEvent.PRIORITY_EVENT));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/awt/EventQueue/SecondaryLoopTest/SecondaryLoopTest.java	Tue Aug 24 12:54:46 2010 +0400
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2010, 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 6949936
+  @author Artem Ananiev: area=eventqueue
+  @run main/timeout=30 SecondaryLoopTest
+*/
+
+import java.awt.*;
+
+/**
+ * Unit test for java.awt.SecondaryLoop implementation
+ */
+public class SecondaryLoopTest {
+
+    private static volatile boolean loopStarted;
+    private static volatile boolean doubleEntered;
+    private static volatile boolean loopActive;
+    private static volatile boolean eventDispatched;
+
+    public static void main(String[] args) throws Exception {
+        test(true, true);
+        test(true, false);
+        test(false, true);
+        test(false, false);
+    }
+
+    private static void test(final boolean enterEDT, final boolean exitEDT) throws Exception {
+        System.out.println("Running test(" + enterEDT + ", " + exitEDT + ")");
+        System.err.flush();
+        loopStarted = true;
+        Runnable enterRun = new Runnable() {
+            @Override
+            public void run() {
+                Toolkit tk = Toolkit.getDefaultToolkit();
+                EventQueue eq = tk.getSystemEventQueue();
+                final SecondaryLoop loop = eq.createSecondaryLoop();
+                doubleEntered = false;
+                eventDispatched = false;
+                Runnable eventRun = new Runnable() {
+                    @Override
+                    public void run() {
+                        // Let the loop enter
+                        sleep(1000);
+                        if (loop.enter()) {
+                            doubleEntered = true;
+                        }
+                        eventDispatched = true;
+                    }
+                };
+                EventQueue.invokeLater(eventRun);
+                Runnable exitRun = new Runnable() {
+                    @Override
+                    public void run() {
+                        // Let the loop enter and eventRun finish
+                        sleep(2000);
+                        if (doubleEntered) {
+                            // Hopefully, we get here if the loop is entered twice
+                            loop.exit();
+                        }
+                        loop.exit();
+                    }
+                };
+                if (exitEDT) {
+                    EventQueue.invokeLater(exitRun);
+                } else {
+                    new Thread(exitRun).start();
+                }
+                if (!loop.enter()) {
+                    loopStarted = false;
+                }
+                loopActive = eventDispatched;
+            }
+        };
+        if (enterEDT) {
+            EventQueue.invokeAndWait(enterRun);
+        } else {
+            enterRun.run();
+        }
+        // Print all the flags before we fail with exception
+        System.out.println("    loopStarted = " + loopStarted);
+        System.out.println("    doubleEntered = " + doubleEntered);
+        System.out.println("    loopActive = " + loopActive);
+        System.out.flush();
+        if (!loopStarted) {
+            throw new RuntimeException("Test FAILED: the secondary loop is not started");
+        }
+        if (doubleEntered) {
+            throw new RuntimeException("Test FAILED: the secondary loop is started twice");
+        }
+        if (!loopActive) {
+            throw new RuntimeException("Test FAILED: the secondary loop exited immediately");
+        }
+    }
+
+    private static void sleep(long t) {
+        try {
+            Thread.sleep(t);
+        } catch (InterruptedException e) {
+            e.printStackTrace(System.err);
+        }
+    }
+
+}