1 /*
   2  * Copyright (c) 2000, 2015, 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 import java.security.AccessController;
  30 import java.security.PrivilegedAction;
  31 import java.util.HashSet;
  32 import java.util.IdentityHashMap;
  33 import java.util.Map;
  34 import java.util.Set;
  35 
  36 import sun.awt.util.ThreadGroupUtils;
  37 import sun.misc.ManagedLocalsThread;
  38 import sun.util.logging.PlatformLogger;
  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} - the map between the existing peer objects
  45  *     and their associated targets
  46  * <li>{@code toolkitThreadBusy} - 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} - 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} is empty and {@code toolkitThreadBusy}
  55  * is false and {@code busyThreadSet} is empty.
  56  * The internal AWTAutoShutdown logic secures that the single non-daemon
  57  * thread ({@code blockerThread}) 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                     activateBlockerThread();
 221                 } else {
 222                     mainLock.notifyAll();
 223                     timeoutPassed = false;
 224                 }
 225             }
 226         }
 227     }
 228 
 229     /**
 230      * Determine whether AWT is currently in ready-to-shutdown state.
 231      * AWT is considered to be in ready-to-shutdown state if
 232      * {@code peerMap} is empty and {@code toolkitThreadBusy}
 233      * is false and {@code busyThreadSet} is empty.
 234      *
 235      * @return true if AWT is in ready-to-shutdown state.
 236      */
 237     private boolean isReadyToShutdown() {
 238         return (!toolkitThreadBusy &&
 239                  peerMap.isEmpty() &&
 240                  busyThreadSet.isEmpty());
 241     }
 242 
 243     /**
 244      * Notify about the toolkit thread state change.
 245      *
 246      * @param busy true if the toolkit thread state changes from idle
 247      *             to busy.
 248      * @see     AWTAutoShutdown#notifyToolkitThreadBusy
 249      * @see     AWTAutoShutdown#notifyToolkitThreadFree
 250      * @see     AWTAutoShutdown#isReadyToShutdown
 251      */
 252     private void setToolkitBusy(final boolean busy) {
 253         if (busy != toolkitThreadBusy) {
 254             synchronized (activationLock) {
 255                 synchronized (mainLock) {
 256                     if (busy != toolkitThreadBusy) {
 257                         if (busy) {
 258                             if (blockerThread == null) {
 259                                 activateBlockerThread();
 260                             } else if (isReadyToShutdown()) {
 261                                 mainLock.notifyAll();
 262                                 timeoutPassed = false;
 263                             }
 264                             toolkitThreadBusy = busy;
 265                         } else {
 266                             toolkitThreadBusy = busy;
 267                             if (isReadyToShutdown()) {
 268                                 mainLock.notifyAll();
 269                                 timeoutPassed = false;
 270                             }
 271                         }
 272                     }
 273                 }
 274             }
 275         }
 276     }
 277 
 278     /**
 279      * Implementation of the Runnable interface.
 280      * Incapsulates the blocker thread functionality.
 281      *
 282      * @see     AWTAutoShutdown#isReadyToShutdown
 283      */
 284     public void run() {
 285         Thread currentThread = Thread.currentThread();
 286         boolean interrupted = false;
 287         synchronized (mainLock) {
 288             try {
 289                 /* Notify that the thread is started. */
 290                 mainLock.notifyAll();
 291                 while (blockerThread == currentThread) {
 292                     mainLock.wait();
 293                     timeoutPassed = false;
 294                     /*
 295                      * This loop is introduced to handle the following case:
 296                      * it is possible that while we are waiting for the
 297                      * safety timeout to pass AWT state can change to
 298                      * not-ready-to-shutdown and back to ready-to-shutdown.
 299                      * In this case we have to wait once again.
 300                      * NOTE: we shouldn't break into the outer loop
 301                      * in this case, since we may never be notified
 302                      * in an outer infinite wait at this point.
 303                      */
 304                     while (isReadyToShutdown()) {
 305                         if (timeoutPassed) {
 306                             timeoutPassed = false;
 307                             blockerThread = null;
 308                             break;
 309                         }
 310                         timeoutPassed = true;
 311                         mainLock.wait(SAFETY_TIMEOUT);
 312                     }
 313                 }
 314             } catch (InterruptedException e) {
 315                 interrupted = true;
 316             } finally {
 317                 if (blockerThread == currentThread) {
 318                     blockerThread = null;
 319                 }
 320             }
 321         }
 322         if (!interrupted) {
 323             AppContext.stopEventDispatchThreads();
 324         }
 325     }
 326 
 327     @SuppressWarnings("serial")
 328     static AWTEvent getShutdownEvent() {
 329         return new AWTEvent(getInstance(), 0) {
 330         };
 331     }
 332 
 333     /**
 334      * Creates and starts a new blocker thread. Doesn't return until
 335      * the new blocker thread starts.
 336      */
 337     private void activateBlockerThread() {
 338         AccessController.doPrivileged((PrivilegedAction<Thread>) () -> {
 339             String name = "AWT-Shutdown";
 340             Thread thread = new ManagedLocalsThread(
 341                     ThreadGroupUtils.getRootThreadGroup(), this, name);
 342             thread.setContextClassLoader(null);
 343             thread.setDaemon(false);
 344             blockerThread = thread;
 345             return thread;
 346         }).start();
 347         try {
 348             /* Wait for the blocker thread to start. */
 349             mainLock.wait();
 350         } catch (InterruptedException e) {
 351             System.err.println("AWT blocker activation interrupted:");
 352             e.printStackTrace();
 353         }
 354     }
 355 
 356     void registerPeer(final Object target, final Object peer) {
 357         synchronized (activationLock) {
 358             synchronized (mainLock) {
 359                 peerMap.put(target, peer);
 360                 notifyPeerMapUpdated();
 361             }
 362         }
 363     }
 364 
 365     void unregisterPeer(final Object target, final Object peer) {
 366         synchronized (activationLock) {
 367             synchronized (mainLock) {
 368                 if (peerMap.get(target) == peer) {
 369                     peerMap.remove(target);
 370                     notifyPeerMapUpdated();
 371                 }
 372             }
 373         }
 374     }
 375 
 376     Object getPeer(final Object target) {
 377         synchronized (activationLock) {
 378             synchronized (mainLock) {
 379                 return peerMap.get(target);
 380             }
 381         }
 382     }
 383 
 384     void dumpPeers(final PlatformLogger aLog) {
 385         if (aLog.isLoggable(PlatformLogger.Level.FINE)) {
 386             synchronized (activationLock) {
 387                 synchronized (mainLock) {
 388                     aLog.fine("Mapped peers:");
 389                     for (Object key : peerMap.keySet()) {
 390                         aLog.fine(key + "->" + peerMap.get(key));
 391                     }
 392                 }
 393             }
 394         }
 395     }
 396 
 397 } // class AWTAutoShutdown
--- EOF ---