/* * Copyright (c) 2000, 2006, 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 sun.awt; import java.awt.AWTEvent; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; import java.util.Set; import sun.util.logging.PlatformLogger; /** * This class is to let AWT shutdown automatically when a user is done * with AWT. It tracks AWT state using the following parameters: *

* AWT is considered to be in ready-to-shutdown state when * peerMap is empty and toolkitThreadBusy * is false and busyThreadSet is empty. * The internal AWTAutoShutdown logic secures that the single non-daemon * thread (blockerThread) is running when AWT is not in * ready-to-shutdown state. This blocker thread is to prevent AWT from * exiting since the toolkit thread is now daemon and all the event * dispatch threads are started only when needed. Once it is detected * that AWT is in ready-to-shutdown state this blocker thread waits * for a certain timeout and if AWT state doesn't change during timeout * this blocker thread terminates all the event dispatch threads and * exits. */ public final class AWTAutoShutdown implements Runnable { private static final AWTAutoShutdown theInstance = new AWTAutoShutdown(); /** * This lock object is used to synchronize shutdown operations. */ private final Object mainLock = new Object(); /** * This lock object is to secure that when a new blocker thread is * started it will be the first who acquire the main lock after * the thread that created the new blocker released the main lock * by calling lock.wait() to wait for the blocker to start. */ private final Object activationLock = new Object(); /** * This set keeps references to all the event dispatch threads that * are busy at this moment, i.e. those that are not waiting for a * new event to appear in their event queue. * Access is synchronized on the main lock object. */ private final Set busyThreadSet = new HashSet<>(7); /** * Indicates whether the toolkit thread is waiting for a new native * event to appear or is dispatching an event. */ private boolean toolkitThreadBusy = false; /** * This is a map between components and their peers. * we should work with in under activationLock&mainLock lock. */ private final Map peerMap = new IdentityHashMap<>(); /** * References the alive non-daemon thread that is currently used * for keeping AWT from exiting. */ private Thread blockerThread = null; /** * We need this flag to secure that AWT state hasn't changed while * we were waiting for the safety timeout to pass. */ private boolean timeoutPassed = false; /** * Once we detect that AWT is ready to shutdown we wait for a certain * timeout to pass before stopping event dispatch threads. */ private static final int SAFETY_TIMEOUT = 1000; /** * Constructor method is intentionally made private to secure * a single instance. Use getInstance() to reference it. * * @see AWTAutoShutdown#getInstance */ private AWTAutoShutdown() {} /** * Returns reference to a single AWTAutoShutdown instance. */ public static AWTAutoShutdown getInstance() { return theInstance; } /** * Notify that the toolkit thread is not waiting for a native event * to appear in its queue. * * @see AWTAutoShutdown#notifyToolkitThreadFree * @see AWTAutoShutdown#setToolkitBusy * @see AWTAutoShutdown#isReadyToShutdown */ public static void notifyToolkitThreadBusy() { getInstance().setToolkitBusy(true); } /** * Notify that the toolkit thread is waiting for a native event * to appear in its queue. * * @see AWTAutoShutdown#notifyToolkitThreadFree * @see AWTAutoShutdown#setToolkitBusy * @see AWTAutoShutdown#isReadyToShutdown */ public static void notifyToolkitThreadFree() { getInstance().setToolkitBusy(false); } /** * Add a specified thread to the set of busy event dispatch threads. * If this set already contains the specified thread or the thread is null, * the call leaves this set unchanged and returns silently. * * @param thread thread to be added to this set, if not present. * @see AWTAutoShutdown#notifyThreadFree * @see AWTAutoShutdown#isReadyToShutdown */ public void notifyThreadBusy(final Thread thread) { if (thread == null) { return; } synchronized (activationLock) { synchronized (mainLock) { if (blockerThread == null) { activateBlockerThread(); } else if (isReadyToShutdown()) { mainLock.notifyAll(); timeoutPassed = false; } busyThreadSet.add(thread); } } } /** * Remove a specified thread from the set of busy event dispatch threads. * If this set doesn't contain the specified thread or the thread is null, * the call leaves this set unchanged and returns silently. * * @param thread thread to be removed from this set, if present. * @see AWTAutoShutdown#notifyThreadBusy * @see AWTAutoShutdown#isReadyToShutdown */ public void notifyThreadFree(final Thread thread) { if (thread == null) { return; } synchronized (activationLock) { synchronized (mainLock) { busyThreadSet.remove(thread); if (isReadyToShutdown()) { mainLock.notifyAll(); timeoutPassed = false; } } } } /** * Notify that the peermap has been updated, that means a new peer * has been created or some existing peer has been disposed. * * @see AWTAutoShutdown#isReadyToShutdown */ void notifyPeerMapUpdated() { synchronized (activationLock) { synchronized (mainLock) { if (!isReadyToShutdown() && blockerThread == null) { activateBlockerThread(); } else { mainLock.notifyAll(); timeoutPassed = false; } } } } /** * Determine whether AWT is currently in ready-to-shutdown state. * AWT is considered to be in ready-to-shutdown state if * peerMap is empty and toolkitThreadBusy * is false and busyThreadSet is empty. * * @return true if AWT is in ready-to-shutdown state. */ public boolean isReadyToShutdown() { // We need to synchronize here since the method is public. // The peerMap states it wants activationLock as well, // but run() doesn't use it when calling this method, // so we won't either. synchronized (mainLock) { return (!toolkitThreadBusy && peerMap.isEmpty() && busyThreadSet.isEmpty()); } } /** * Notify about the toolkit thread state change. * * @param busy true if the toolkit thread state changes from idle * to busy. * @see AWTAutoShutdown#notifyToolkitThreadBusy * @see AWTAutoShutdown#notifyToolkitThreadFree * @see AWTAutoShutdown#isReadyToShutdown */ private void setToolkitBusy(final boolean busy) { if (busy != toolkitThreadBusy) { synchronized (activationLock) { synchronized (mainLock) { if (busy != toolkitThreadBusy) { if (busy) { if (blockerThread == null) { activateBlockerThread(); } else if (isReadyToShutdown()) { mainLock.notifyAll(); timeoutPassed = false; } toolkitThreadBusy = busy; } else { toolkitThreadBusy = busy; if (isReadyToShutdown()) { mainLock.notifyAll(); timeoutPassed = false; } } } } } } } /** * Implementation of the Runnable interface. * Incapsulates the blocker thread functionality. * * @see AWTAutoShutdown#isReadyToShutdown */ public void run() { Thread currentThread = Thread.currentThread(); boolean interrupted = false; synchronized (mainLock) { try { /* Notify that the thread is started. */ mainLock.notifyAll(); while (blockerThread == currentThread) { mainLock.wait(); timeoutPassed = false; /* * This loop is introduced to handle the following case: * it is possible that while we are waiting for the * safety timeout to pass AWT state can change to * not-ready-to-shutdown and back to ready-to-shutdown. * In this case we have to wait once again. * NOTE: we shouldn't break into the outer loop * in this case, since we may never be notified * in an outer infinite wait at this point. */ while (isReadyToShutdown()) { if (timeoutPassed) { timeoutPassed = false; blockerThread = null; break; } timeoutPassed = true; mainLock.wait(SAFETY_TIMEOUT); } } } catch (InterruptedException e) { interrupted = true; } finally { if (blockerThread == currentThread) { blockerThread = null; } } } if (!interrupted) { AppContext.stopEventDispatchThreads(); } } @SuppressWarnings("serial") static AWTEvent getShutdownEvent() { return new AWTEvent(getInstance(), 0) { }; } /** * Creates and starts a new blocker thread. Doesn't return until * the new blocker thread starts. */ private void activateBlockerThread() { Thread thread = new Thread(this, "AWT-Shutdown"); thread.setDaemon(false); blockerThread = thread; thread.start(); try { /* Wait for the blocker thread to start. */ mainLock.wait(); } catch (InterruptedException e) { System.err.println("AWT blocker activation interrupted:"); e.printStackTrace(); } } final void registerPeer(final Object target, final Object peer) { synchronized (activationLock) { synchronized (mainLock) { peerMap.put(target, peer); notifyPeerMapUpdated(); } } } final void unregisterPeer(final Object target, final Object peer) { synchronized (activationLock) { synchronized (mainLock) { if (peerMap.get(target) == peer) { peerMap.remove(target); notifyPeerMapUpdated(); } } } } final Object getPeer(final Object target) { synchronized (activationLock) { synchronized (mainLock) { return peerMap.get(target); } } } final void dumpPeers(final PlatformLogger aLog) { synchronized (activationLock) { synchronized (mainLock) { aLog.fine("Mapped peers:"); for (Object key : peerMap.keySet()) { aLog.fine(key + "->" + peerMap.get(key)); } } } } } // class AWTAutoShutdown