| /* | |
| * Copyright (c) 2000, 2024, 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.HashSet; | |
| import java.util.IdentityHashMap; | |
| import java.util.Map; | |
| import java.util.Set; | |
| import sun.awt.util.ThreadGroupUtils; | |
| 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: | |
| * <ul> | |
| * <li>{@code peerMap} - the map between the existing peer objects | |
| * and their associated targets | |
| * <li>{@code toolkitThreadBusy} - whether the toolkit thread | |
| * is waiting for a new native event to appear in its queue | |
| * or is dispatching an event | |
| * <li>{@code busyThreadSet} - a set of 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. | |
| * </ul><p> | |
| * AWT is considered to be in ready-to-shutdown state when | |
| * {@code peerMap} is empty and {@code toolkitThreadBusy} | |
| * is false and {@code busyThreadSet} is empty. | |
| * The internal AWTAutoShutdown logic secures that the single non-daemon | |
| * thread ({@code 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<Thread> 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<Object, Object> 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 | |
| * {@code peerMap} is empty and {@code toolkitThreadBusy} | |
| * is false and {@code busyThreadSet} is empty. | |
| * | |
| * @return true if AWT is in ready-to-shutdown state. | |
| */ | |
| private boolean isReadyToShutdown() { | |
| 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. | |
| * Encapsulates 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(); | |
| } | |
| } | |
| 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() { | |
| String name = "AWT-Shutdown"; | |
| Thread thread = new Thread(ThreadGroupUtils.getRootThreadGroup(), this, name, 0, false); | |
| thread.setContextClassLoader(null); | |
| 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(); | |
| } | |
| } | |
| void registerPeer(final Object target, final Object peer) { | |
| synchronized (activationLock) { | |
| synchronized (mainLock) { | |
| peerMap.put(target, peer); | |
| notifyPeerMapUpdated(); | |
| } | |
| } | |
| } | |
| void unregisterPeer(final Object target, final Object peer) { | |
| synchronized (activationLock) { | |
| synchronized (mainLock) { | |
| if (peerMap.get(target) == peer) { | |
| peerMap.remove(target); | |
| notifyPeerMapUpdated(); | |
| } | |
| } | |
| } | |
| } | |
| Object getPeer(final Object target) { | |
| synchronized (activationLock) { | |
| synchronized (mainLock) { | |
| return peerMap.get(target); | |
| } | |
| } | |
| } | |
| void dumpPeers(final PlatformLogger aLog) { | |
| if (aLog.isLoggable(PlatformLogger.Level.FINE)) { | |
| synchronized (activationLock) { | |
| synchronized (mainLock) { | |
| aLog.fine("Mapped peers:"); | |
| for (Object key : peerMap.keySet()) { | |
| aLog.fine(key + "->" + peerMap.get(key)); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } // class AWTAutoShutdown |