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     public boolean isReadyToShutdown() {
 236         // We need to synchronize here since the method is public.
 237         // The peerMap states it wants activationLock as well,
 238         // but run() doesn't use it when calling this method,
 239         // so we won't either.
 240         synchronized (mainLock) {
 241             return (!toolkitThreadBusy &&
 242                     peerMap.isEmpty() &&
 243                     busyThreadSet.isEmpty());
 244         }
 245     }
 246 
 247     /**
 248      * Notify about the toolkit thread state change.
 249      *
 250      * @param busy true if the toolkit thread state changes from idle
 251      *             to busy.
 252      * @see     AWTAutoShutdown#notifyToolkitThreadBusy
 253      * @see     AWTAutoShutdown#notifyToolkitThreadFree
 254      * @see     AWTAutoShutdown#isReadyToShutdown
 255      */
 256     private void setToolkitBusy(final boolean busy) {
 257         if (busy != toolkitThreadBusy) {
 258             synchronized (activationLock) {
 259                 synchronized (mainLock) {
 260                     if (busy != toolkitThreadBusy) {
 261                         if (busy) {
 262                             if (blockerThread == null) {
 263                                 activateBlockerThread();
 264                             } else if (isReadyToShutdown()) {
 265                                 mainLock.notifyAll();
 266                                 timeoutPassed = false;
 267                             }
 268                             toolkitThreadBusy = busy;
 269                         } else {
 270                             toolkitThreadBusy = busy;
 271                             if (isReadyToShutdown()) {
 272                                 mainLock.notifyAll();
 273                                 timeoutPassed = false;
 274                             }
 275                         }
 276                     }
 277                 }
 278             }
 279         }
 280     }
 281 
 282     /**
 283      * Implementation of the Runnable interface.
 284      * Incapsulates the blocker thread functionality.
 285      *
 286      * @see     AWTAutoShutdown#isReadyToShutdown
 287      */
 288     public void run() {
 289         Thread currentThread = Thread.currentThread();
 290         boolean interrupted = false;
 291         synchronized (mainLock) {
 292             try {
 293                 /* Notify that the thread is started. */
 294                 mainLock.notifyAll();
 295                 while (blockerThread == currentThread) {
 296                     mainLock.wait();
 297                     timeoutPassed = false;
 298                     /*
 299                      * This loop is introduced to handle the following case:
 300                      * it is possible that while we are waiting for the
 301                      * safety timeout to pass AWT state can change to
 302                      * not-ready-to-shutdown and back to ready-to-shutdown.
 303                      * In this case we have to wait once again.
 304                      * NOTE: we shouldn't break into the outer loop
 305                      * in this case, since we may never be notified
 306                      * in an outer infinite wait at this point.
 307                      */
 308                     while (isReadyToShutdown()) {
 309                         if (timeoutPassed) {
 310                             timeoutPassed = false;
 311                             blockerThread = null;
 312                             break;
 313                         }
 314                         timeoutPassed = true;
 315                         mainLock.wait(SAFETY_TIMEOUT);
 316                     }
 317                 }
 318             } catch (InterruptedException e) {
 319                 interrupted = true;
 320             } finally {
 321                 if (blockerThread == currentThread) {
 322                     blockerThread = null;
 323                 }
 324             }
 325         }
 326         if (!interrupted) {
 327             AppContext.stopEventDispatchThreads();
 328         }
 329     }
 330 
 331     @SuppressWarnings("serial")
 332     static AWTEvent getShutdownEvent() {
 333         return new AWTEvent(getInstance(), 0) {
 334         };
 335     }
 336 
 337     /**
 338      * Creates and starts a new blocker thread. Doesn't return until
 339      * the new blocker thread starts.
 340      */
 341     private void activateBlockerThread() {
 342         Thread thread = new Thread(this, "AWT-Shutdown");
 343         thread.setDaemon(false);
 344         blockerThread = thread;
 345         thread.start();
 346         try {
 347             /* Wait for the blocker thread to start. */
 348             mainLock.wait();
 349         } catch (InterruptedException e) {
 350             System.err.println("AWT blocker activation interrupted:");
 351             e.printStackTrace();
 352         }
 353     }
 354 
 355     final void registerPeer(final Object target, final Object peer) {
 356         synchronized (activationLock) {
 357             synchronized (mainLock) {
 358                 peerMap.put(target, peer);
 359                 notifyPeerMapUpdated();
 360             }
 361         }
 362     }
 363 
 364     final void unregisterPeer(final Object target, final Object peer) {
 365         synchronized (activationLock) {
 366             synchronized (mainLock) {
 367                 if (peerMap.get(target) == peer) {
 368                     peerMap.remove(target);
 369                     notifyPeerMapUpdated();
 370                 }
 371             }
 372         }
 373     }
 374 
 375     final Object getPeer(final Object target) {
 376         synchronized (activationLock) {
 377             synchronized (mainLock) {
 378                 return peerMap.get(target);
 379             }
 380         }
 381     }
 382 
 383     final void dumpPeers(final PlatformLogger aLog) {
 384         synchronized (activationLock) {
 385             synchronized (mainLock) {
 386                 aLog.fine("Mapped peers:");
 387                 for (Object key : peerMap.keySet()) {
 388                     aLog.fine(key + "->" + peerMap.get(key));
 389                 }
 390             }
 391         }
 392     }
 393 
 394 } // class AWTAutoShutdown