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