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