1 /*
   2  * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.awt;
  27 
  28 import java.awt.AWTEvent;
  29 
  30 import java.util.Collections;
  31 import java.util.HashSet;
  32 import java.util.IdentityHashMap;
  33 import java.util.Map;
  34 import java.util.Set;
  35 
  36 import sun.util.logging.PlatformLogger;
  37 
  38 /**
  39  * This class is to let AWT shutdown automatically when a user is done
  40  * with AWT. It tracks AWT state using the following parameters:
  41  * <ul>
  42  * <li><code>peerMap</code> - the map between the existing peer objects
  43  *     and their associated targets
  44  * <li><code>toolkitThreadBusy</code> - whether the toolkit thread
  45  *     is waiting for a new native event to appear in its queue
  46  *     or is dispatching an event
  47  * <li><code>busyThreadSet</code> - a set of all the event dispatch
  48  *     threads that are busy at this moment, i.e. those that are not
  49  *     waiting for a new event to appear in their event queue.
  50  * </ul><p>
  51  * AWT is considered to be in ready-to-shutdown state when
  52  * <code>peerMap</code> is empty and <code>toolkitThreadBusy</code>
  53  * is false and <code>busyThreadSet</code> is empty.
  54  * The internal AWTAutoShutdown logic secures that the single non-daemon
  55  * thread (<code>blockerThread</code>) is running when AWT is not in
  56  * ready-to-shutdown state. This blocker thread is to prevent AWT from
  57  * exiting since the toolkit thread is now daemon and all the event
  58  * dispatch threads are started only when needed. Once it is detected
  59  * that AWT is in ready-to-shutdown state this blocker thread waits
  60  * for a certain timeout and if AWT state doesn't change during timeout
  61  * this blocker thread terminates all the event dispatch threads and
  62  * exits.
  63  */
  64 public final class AWTAutoShutdown implements Runnable {
  65 
  66     private static final AWTAutoShutdown theInstance = new AWTAutoShutdown();
  67 
  68     /**
  69      * This lock object is used to synchronize shutdown operations.
  70      */
  71     private final Object mainLock = new Object();
  72 
  73     /**
  74      * This lock object is to secure that when a new blocker thread is
  75      * started it will be the first who acquire the main lock after
  76      * the thread that created the new blocker released the main lock
  77      * by calling lock.wait() to wait for the blocker to start.
  78      */
  79     private final Object activationLock = new Object();
  80 
  81     /**
  82      * This set keeps references to all the event dispatch threads that
  83      * are busy at this moment, i.e. those that are not waiting for a
  84      * new event to appear in their event queue.
  85      * Access is synchronized on the main lock object.
  86      */
  87     private final Set<Thread> busyThreadSet = new HashSet<>(7);
  88 
  89     /**
  90      * Indicates whether the toolkit thread is waiting for a new native
  91      * event to appear or is dispatching an event.
  92      */
  93     private boolean toolkitThreadBusy = false;
  94 
  95     /**
  96      * This is a map between components and their peers.
  97      * we should work with in under activationLock&mainLock lock.
  98      */
  99     private final Map<Object, Object> peerMap = new IdentityHashMap<>();
 100 
 101     /**
 102      * References the alive non-daemon thread that is currently used
 103      * for keeping AWT from exiting.
 104      */
 105     private Thread blockerThread = null;
 106 
 107     /**
 108      * We need this flag to secure that AWT state hasn't changed while
 109      * we were waiting for the safety timeout to pass.
 110      */
 111     private boolean timeoutPassed = false;
 112 
 113     /**
 114      * Once we detect that AWT is ready to shutdown we wait for a certain
 115      * timeout to pass before stopping event dispatch threads.
 116      */
 117     private static final int SAFETY_TIMEOUT = 1000;
 118 
 119     /**
 120      * Constructor method is intentionally made private to secure
 121      * a single instance. Use getInstance() to reference it.
 122      *
 123      * @see     AWTAutoShutdown#getInstance
 124      */
 125     private AWTAutoShutdown() {}
 126 
 127     /**
 128      * Returns reference to a single AWTAutoShutdown instance.
 129      */
 130     public static AWTAutoShutdown getInstance() {
 131         return theInstance;
 132     }
 133 
 134     /**
 135      * Notify that the toolkit thread is not waiting for a native event
 136      * to appear in its queue.
 137      *
 138      * @see     AWTAutoShutdown#notifyToolkitThreadFree
 139      * @see     AWTAutoShutdown#setToolkitBusy
 140      * @see     AWTAutoShutdown#isReadyToShutdown
 141      */
 142     public static void notifyToolkitThreadBusy() {
 143         getInstance().setToolkitBusy(true);
 144     }
 145 
 146     /**
 147      * Notify that the toolkit thread is waiting for a native event
 148      * to appear in its queue.
 149      *
 150      * @see     AWTAutoShutdown#notifyToolkitThreadFree
 151      * @see     AWTAutoShutdown#setToolkitBusy
 152      * @see     AWTAutoShutdown#isReadyToShutdown
 153      */
 154     public static void notifyToolkitThreadFree() {
 155         getInstance().setToolkitBusy(false);
 156     }
 157 
 158     /**
 159      * Add a specified thread to the set of busy event dispatch threads.
 160      * If this set already contains the specified thread or the thread is null,
 161      * the call leaves this set unchanged and returns silently.
 162      *
 163      * @param thread thread to be added to this set, if not present.
 164      * @see     AWTAutoShutdown#notifyThreadFree
 165      * @see     AWTAutoShutdown#isReadyToShutdown
 166      */
 167     public void notifyThreadBusy(final Thread thread) {
 168         if (thread == null) {
 169             return;
 170         }
 171         synchronized (activationLock) {
 172             synchronized (mainLock) {
 173                 if (blockerThread == null) {
 174                     activateBlockerThread();
 175                 } else if (isReadyToShutdown()) {
 176                     mainLock.notifyAll();
 177                     timeoutPassed = false;
 178                 }
 179                 busyThreadSet.add(thread);
 180             }
 181         }
 182     }
 183 
 184     /**
 185      * Remove a specified thread from the set of busy event dispatch threads.
 186      * If this set doesn't contain the specified thread or the thread is null,
 187      * the call leaves this set unchanged and returns silently.
 188      *
 189      * @param thread thread to be removed from this set, if present.
 190      * @see     AWTAutoShutdown#notifyThreadBusy
 191      * @see     AWTAutoShutdown#isReadyToShutdown
 192      */
 193     public void notifyThreadFree(final Thread thread) {
 194         if (thread == null) {
 195             return;
 196         }
 197         synchronized (activationLock) {
 198             synchronized (mainLock) {
 199                 busyThreadSet.remove(thread);
 200                 if (isReadyToShutdown()) {
 201                     mainLock.notifyAll();
 202                     timeoutPassed = false;
 203                 }
 204             }
 205         }
 206     }
 207 
 208     /**
 209      * Notify that the peermap has been updated, that means a new peer
 210      * has been created or some existing peer has been disposed.
 211      *
 212      * @see     AWTAutoShutdown#isReadyToShutdown
 213      */
 214     void notifyPeerMapUpdated() {
 215         synchronized (activationLock) {
 216             synchronized (mainLock) {
 217                 if (!isReadyToShutdown() && blockerThread == null) {
 218                     activateBlockerThread();
 219                 } else {
 220                     mainLock.notifyAll();
 221                     timeoutPassed = false;
 222                 }
 223             }
 224         }
 225     }
 226 
 227     /**
 228      * Determine whether AWT is currently in ready-to-shutdown state.
 229      * AWT is considered to be in ready-to-shutdown state if
 230      * <code>peerMap</code> is empty and <code>toolkitThreadBusy</code>
 231      * is false and <code>busyThreadSet</code> is empty.
 232      *
 233      * @return true if AWT is in ready-to-shutdown state.
 234      */
 235     private boolean isReadyToShutdown() {
 236         return (!toolkitThreadBusy &&
 237                  peerMap.isEmpty() &&
 238                  busyThreadSet.isEmpty());
 239     }
 240 
 241     /**
 242      * Notify about the toolkit thread state change.
 243      *
 244      * @param busy true if the toolkit thread state changes from idle
 245      *             to busy.
 246      * @see     AWTAutoShutdown#notifyToolkitThreadBusy
 247      * @see     AWTAutoShutdown#notifyToolkitThreadFree
 248      * @see     AWTAutoShutdown#isReadyToShutdown
 249      */
 250     private void setToolkitBusy(final boolean busy) {
 251         if (busy != toolkitThreadBusy) {
 252             synchronized (activationLock) {
 253                 synchronized (mainLock) {
 254                     if (busy != toolkitThreadBusy) {
 255                         if (busy) {
 256                             if (blockerThread == null) {
 257                                 activateBlockerThread();
 258                             } else if (isReadyToShutdown()) {
 259                                 mainLock.notifyAll();
 260                                 timeoutPassed = false;
 261                             }
 262                             toolkitThreadBusy = busy;
 263                         } else {
 264                             toolkitThreadBusy = busy;
 265                             if (isReadyToShutdown()) {
 266                                 mainLock.notifyAll();
 267                                 timeoutPassed = false;
 268                             }
 269                         }
 270                     }
 271                 }
 272             }
 273         }
 274     }
 275 
 276     /**
 277      * Implementation of the Runnable interface.
 278      * Incapsulates the blocker thread functionality.
 279      *
 280      * @see     AWTAutoShutdown#isReadyToShutdown
 281      */
 282     public void run() {
 283         Thread currentThread = Thread.currentThread();
 284         boolean interrupted = false;
 285         synchronized (mainLock) {
 286             try {
 287                 /* Notify that the thread is started. */
 288                 mainLock.notifyAll();
 289                 while (blockerThread == currentThread) {
 290                     mainLock.wait();
 291                     timeoutPassed = false;
 292                     /*
 293                      * This loop is introduced to handle the following case:
 294                      * it is possible that while we are waiting for the
 295                      * safety timeout to pass AWT state can change to
 296                      * not-ready-to-shutdown and back to ready-to-shutdown.
 297                      * In this case we have to wait once again.
 298                      * NOTE: we shouldn't break into the outer loop
 299                      * in this case, since we may never be notified
 300                      * in an outer infinite wait at this point.
 301                      */
 302                     while (isReadyToShutdown()) {
 303                         if (timeoutPassed) {
 304                             timeoutPassed = false;
 305                             blockerThread = null;
 306                             break;
 307                         }
 308                         timeoutPassed = true;
 309                         mainLock.wait(SAFETY_TIMEOUT);
 310                     }
 311                 }
 312             } catch (InterruptedException e) {
 313                 interrupted = true;
 314             } finally {
 315                 if (blockerThread == currentThread) {
 316                     blockerThread = null;
 317                 }
 318             }
 319         }
 320         if (!interrupted) {
 321             AppContext.stopEventDispatchThreads();
 322         }
 323     }
 324 
 325     @SuppressWarnings("serial")
 326     static AWTEvent getShutdownEvent() {
 327         return new AWTEvent(getInstance(), 0) {
 328         };
 329     }
 330 
 331     /**
 332      * Creates and starts a new blocker thread. Doesn't return until
 333      * the new blocker thread starts.
 334      */
 335     private void activateBlockerThread() {
 336         Thread thread = new Thread(this, "AWT-Shutdown");
 337         thread.setDaemon(false);
 338         blockerThread = thread;
 339         thread.start();
 340         try {
 341             /* Wait for the blocker thread to start. */
 342             mainLock.wait();
 343         } catch (InterruptedException e) {
 344             System.err.println("AWT blocker activation interrupted:");
 345             e.printStackTrace();
 346         }
 347     }
 348 
 349     final void registerPeer(final Object target, final Object peer) {
 350         synchronized (activationLock) {
 351             synchronized (mainLock) {
 352                 peerMap.put(target, peer);
 353                 notifyPeerMapUpdated();
 354             }
 355         }
 356     }
 357 
 358     final void unregisterPeer(final Object target, final Object peer) {
 359         synchronized (activationLock) {
 360             synchronized (mainLock) {
 361                 if (peerMap.get(target) == peer) {
 362                     peerMap.remove(target);
 363                     notifyPeerMapUpdated();
 364                 }
 365             }
 366         }
 367     }
 368 
 369     final Object getPeer(final Object target) {
 370         synchronized (activationLock) {
 371             synchronized (mainLock) {
 372                 return peerMap.get(target);
 373             }
 374         }
 375     }
 376 
 377     final void dumpPeers(final PlatformLogger aLog) {
 378         if (aLog.isLoggable(PlatformLogger.FINE)) {
 379             synchronized (activationLock) {
 380                 synchronized (mainLock) {
 381                     aLog.fine("Mapped peers:");
 382                     for (Object key : peerMap.keySet()) {
 383                         aLog.fine(key + "->" + peerMap.get(key));
 384                     }
 385                 }
 386             }
 387         }
 388     }
 389 
 390 } // class AWTAutoShutdown