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