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
26package sun.awt;
27
28import java.awt.AWTEvent;
29import java.security.AccessController;
30import java.security.PrivilegedAction;
31import java.util.HashSet;
32import java.util.IdentityHashMap;
33import java.util.Map;
34import java.util.Set;
35
36import sun.awt.util.ThreadGroupUtils;
37import 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 */
65public 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     */
126    private AWTAutoShutdown() {}
127
128    /**
129     * Returns reference to a single AWTAutoShutdown instance.
130     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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")
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     */
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
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
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
375    Object getPeer(final Object target) {
376        synchronized (activationLock) {
377            synchronized (mainLock) {
378                return peerMap.get(target);
379            }
380        }
381    }
382
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