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