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