/*
* Copyright (c) 2000, 2015, 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.security.AccessController;
import java.security.PrivilegedAction;
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:
peerMap
- the map between the existing peer objects and their associated targets toolkitThreadBusy
- whether the toolkit thread is waiting for a new native event to appear in its queue or is dispatching an event 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.
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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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 Also: - getInstance.getInstance
/**
* 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.
/**
* 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 Also: - notifyToolkitThreadFree.notifyToolkitThreadFree
- setToolkitBusy
- isReadyToShutdown
/**
* 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 Also: - notifyToolkitThreadFree.notifyToolkitThreadFree
- setToolkitBusy
- isReadyToShutdown
/**
* 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.
Params: - thread – thread to be added to this set, if not present.
See Also:
/**
* 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.
Params: - thread – thread to be removed from this set, if present.
See Also:
/**
* 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 Also: - isReadyToShutdown.isReadyToShutdown
/**
* 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. Returns: true if AWT is in ready-to-shutdown state.
/**
* 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.
Params: - busy – true if the toolkit thread state changes from idle
to busy.
See Also:
/**
* 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 Also: - isReadyToShutdown.isReadyToShutdown
/**
* 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.
/**
* Creates and starts a new blocker thread. Doesn't return until
* the new blocker thread starts.
*/
private void activateBlockerThread() {
AccessController.doPrivileged((PrivilegedAction<Thread>) () -> {
String name = "AWT-Shutdown";
Thread thread = new Thread(
ThreadGroupUtils.getRootThreadGroup(), this, name, 0, false);
thread.setContextClassLoader(null);
thread.setDaemon(false);
blockerThread = thread;
return 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