001package jmri.managers; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import sun.misc.Signal; 006import sun.misc.SignalHandler; 007 008import java.awt.Frame; 009import java.awt.GraphicsEnvironment; 010import java.awt.event.WindowEvent; 011 012import java.util.*; 013import java.util.concurrent.*; 014 015import jmri.ShutDownManager; 016import jmri.ShutDownTask; 017import jmri.util.SystemType; 018 019import jmri.beans.Bean; 020import jmri.util.ThreadingUtil; 021 022/** 023 * The default implementation of {@link ShutDownManager}. This implementation 024 * makes the following assumptions: 025 * <ul> 026 * <li>The {@link #shutdown()} and {@link #restart()} methods are called on the 027 * application's main thread.</li> 028 * <li>If the application has a graphical user interface, the application's main 029 * thread is the event dispatching thread.</li> 030 * <li>Application windows may contain code that <em>should</em> be run within a 031 * registered {@link ShutDownTask#run()} method, but are not. A side effect 032 * of this assumption is that <em>all</em> displayable application windows are 033 * closed by this implementation when shutdown() or restart() is called and a 034 * ShutDownTask has not aborted the shutdown or restart.</li> 035 * <li>It is expected that SIGINT and SIGTERM should trigger a clean application 036 * exit.</li> 037 * </ul> 038 * <p> 039 * If another implementation of ShutDownManager has not been registered with the 040 * {@link jmri.InstanceManager}, an instance of this implementation will be 041 * automatically registered as the ShutDownManager. 042 * <p> 043 * Developers other applications that cannot accept the above assumptions are 044 * recommended to create their own implementations of ShutDownManager that 045 * integrates with their application's lifecycle and register that 046 * implementation with the InstanceManager as soon as possible in their 047 * application. 048 * 049 * @author Bob Jacobsen Copyright (C) 2008 050 */ 051public class DefaultShutDownManager extends Bean implements ShutDownManager { 052 053 private static volatile boolean shuttingDown = false; 054 055 private final Set<Callable<Boolean>> callables = new CopyOnWriteArraySet<>(); 056 private final Set<EarlyTask> earlyRunnables = new CopyOnWriteArraySet<>(); 057 private final Set<Runnable> runnables = new CopyOnWriteArraySet<>(); 058 059 protected final Thread shutdownHook; 060 061 // 30secs to complete EarlyTasks, 30 secs to complete Main tasks. 062 // package private for testing 063 int tasksTimeOutMilliSec = 30000; 064 065 private static final String NO_NULL_TASK = "Shutdown task cannot be null."; // NOI18N 066 private static final String PROP_SHUTTING_DOWN = "shuttingDown"; // NOI18N 067 068 private boolean blockingShutdown = false; // Used by tests 069 070 /** 071 * Create a new shutdown manager. 072 */ 073 public DefaultShutDownManager() { 074 super(false); 075 // This shutdown hook allows us to perform a clean shutdown when 076 // running in headless mode and SIGINT (Ctrl-C) or SIGTERM. It 077 // executes the shutdown tasks without calling System.exit() since 078 // calling System.exit() within a shutdown hook will cause the 079 // application to hang. 080 // This shutdown hook also allows OS X Application->Quit to trigger our 081 // shutdown tasks, since that simply calls System.exit() 082 this.shutdownHook = ThreadingUtil.newThread(() -> DefaultShutDownManager.this.shutdown(0, false)); 083 try { 084 Runtime.getRuntime().addShutdownHook(this.shutdownHook); 085 } catch (IllegalStateException ex) { 086 // thrown only if System.exit() has been called, so ignore 087 } 088 089 // register a Signal handlers that do shutdown 090 try { 091 if (SystemType.isMacOSX() || SystemType.isLinux()) { 092 SignalHandler handler = new SignalHandler () { 093 @Override 094 public void handle(Signal sig) { 095 shutdown(); 096 } 097 }; 098 Signal.handle(new Signal("TERM"), handler); 099 Signal.handle(new Signal("INT"), handler); 100 101 handler = new SignalHandler () { 102 @Override 103 public void handle(Signal sig) { 104 restart(); 105 } 106 }; 107 Signal.handle(new Signal("HUP"), handler); 108 } 109 110 else if (SystemType.isWindows()) { 111 SignalHandler handler = new SignalHandler () { 112 @Override 113 public void handle(Signal sig) { 114 shutdown(); 115 } 116 }; 117 Signal.handle(new Signal("TERM"), handler); 118 } 119 120 } catch (NullPointerException e) { 121 log.warn("Failed to add signal handler due to missing signal definition"); 122 } 123 } 124 125 /** 126 * Set if shutdown should block GUI/Layout thread. 127 * @param value true if blocking, false otherwise 128 */ 129 public void setBlockingShutdown(boolean value) { 130 blockingShutdown = value; 131 } 132 133 /** 134 * {@inheritDoc} 135 */ 136 @Override 137 public synchronized void register(ShutDownTask s) { 138 Objects.requireNonNull(s, NO_NULL_TASK); 139 this.earlyRunnables.add(new EarlyTask(s)); 140 this.runnables.add(s); 141 this.callables.add(s); 142 this.addPropertyChangeListener(PROP_SHUTTING_DOWN, s); 143 } 144 145 /** 146 * {@inheritDoc} 147 */ 148 @Override 149 public synchronized void register(Callable<Boolean> task) { 150 Objects.requireNonNull(task, NO_NULL_TASK); 151 this.callables.add(task); 152 } 153 154 /** 155 * {@inheritDoc} 156 */ 157 @Override 158 public synchronized void register(Runnable task) { 159 Objects.requireNonNull(task, NO_NULL_TASK); 160 this.runnables.add(task); 161 } 162 163 /** 164 * {@inheritDoc} 165 */ 166 @Override 167 public synchronized void deregister(ShutDownTask s) { 168 this.removePropertyChangeListener(PROP_SHUTTING_DOWN, s); 169 this.callables.remove(s); 170 this.runnables.remove(s); 171 for (EarlyTask r : earlyRunnables) { 172 if (r.task == s) earlyRunnables.remove(r); 173 } 174 } 175 176 /** 177 * {@inheritDoc} 178 */ 179 @Override 180 public synchronized void deregister(Callable<Boolean> task) { 181 this.callables.remove(task); 182 } 183 184 /** 185 * {@inheritDoc} 186 */ 187 @Override 188 public synchronized void deregister(Runnable task) { 189 this.runnables.remove(task); 190 } 191 192 /** 193 * {@inheritDoc} 194 */ 195 @Override 196 public List<Callable<Boolean>> getCallables() { 197 List<Callable<Boolean>> list = new ArrayList<>(); 198 list.addAll(callables); 199 return Collections.unmodifiableList(list); 200 } 201 202 /** 203 * {@inheritDoc} 204 */ 205 @Override 206 public List<Runnable> getRunnables() { 207 List<Runnable> list = new ArrayList<>(); 208 list.addAll(runnables); 209 return Collections.unmodifiableList(list); 210 } 211 212 /** 213 * {@inheritDoc} 214 */ 215 @Override 216 public void shutdown() { 217 shutdown(0, true); 218 } 219 220 /** 221 * {@inheritDoc} 222 */ 223 @Override 224 public void restart() { 225 shutdown(100, true); 226 } 227 228 /** 229 * {@inheritDoc} 230 */ 231 @Override 232 public void restartOS() { 233 shutdown(210, true); 234 } 235 236 /** 237 * {@inheritDoc} 238 */ 239 @Override 240 public void shutdownOS() { 241 shutdown(200, true); 242 } 243 244 /** 245 * First asks the shutdown tasks if shutdown is allowed. 246 * Returns if the shutdown was aborted by the user, in which case the program 247 * should continue to operate. 248 * <p> 249 * After this check does not return under normal circumstances. 250 * Closes any displayable windows. 251 * Executes all registered {@link jmri.ShutDownTask} 252 * Runs the Early shutdown tasks, the main shutdown tasks, 253 * then terminates the program with provided status. 254 * 255 * @param status integer status on program exit 256 * @param exit true if System.exit() should be called if all tasks are 257 * executed correctly; false otherwise 258 */ 259 public void shutdown(int status, boolean exit) { 260 Runnable shutdownTask = () -> { doShutdown(status, exit); }; 261 262 if (!blockingShutdown) { 263 new Thread(shutdownTask).start(); 264 } else { 265 shutdownTask.run(); 266 } 267 } 268 269 /** 270 * First asks the shutdown tasks if shutdown is allowed. 271 * Returns if the shutdown was aborted by the user, in which case the program 272 * should continue to operate. 273 * <p> 274 * After this check does not return under normal circumstances. 275 * Closes any displayable windows. 276 * Executes all registered {@link jmri.ShutDownTask} 277 * Runs the Early shutdown tasks, the main shutdown tasks, 278 * then terminates the program with provided status. 279 * <p> 280 * 281 * @param status integer status on program exit 282 * @param exit true if System.exit() should be called if all tasks are 283 * executed correctly; false otherwise 284 */ 285 @SuppressFBWarnings(value = "DM_EXIT", justification = "OK to directly exit standalone main") 286 private void doShutdown(int status, boolean exit) { 287 log.debug("shutdown called with {} {}", status, exit); 288 if (!shuttingDown) { 289 Date start = new Date(); 290 log.debug("Shutting down with {} callable and {} runnable tasks", 291 callables.size(), runnables.size()); 292 setShuttingDown(true); 293 // First check if shut down is allowed 294 for (Callable<Boolean> task : callables) { 295 try { 296 if (Boolean.FALSE.equals(task.call())) { 297 setShuttingDown(false); 298 return; 299 } 300 } catch (Exception ex) { 301 log.error("Unable to stop", ex); 302 setShuttingDown(false); 303 return; 304 } 305 } 306 // close any open windows by triggering a closing event 307 // this gives open windows a final chance to perform any cleanup 308 if (!GraphicsEnvironment.isHeadless()) { 309 Arrays.asList(Frame.getFrames()).stream().forEach(frame -> { 310 // do not run on thread, or in parallel, as System.exit() 311 // will get called before windows can close 312 if (frame.isDisplayable()) { // dispose() has not been called 313 log.debug("Closing frame \"{}\", title: \"{}\"", frame.getName(), frame.getTitle()); 314 Date timer = new Date(); 315 frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); 316 log.debug("Frame \"{}\" took {} milliseconds to close", frame.getName(), new Date().getTime() - timer.getTime()); 317 } 318 }); 319 } 320 log.debug("windows completed closing {} milliseconds after starting shutdown", new Date().getTime() - start.getTime()); 321 322 // wait for parallel tasks to complete 323 runShutDownTasks(new HashSet<>(earlyRunnables), "JMRI ShutDown - Early Tasks"); 324 325 jmri.util.ThreadingUtil.runOnGUI(() -> { 326 jmri.configurexml.StoreAndCompare.requestStoreIfNeeded(); 327 }); 328 329 // wait for parallel tasks to complete 330 runShutDownTasks(runnables, "JMRI ShutDown - Main Tasks"); 331 332 // success 333 log.debug("Shutdown took {} milliseconds.", new Date().getTime() - start.getTime()); 334 log.info("Normal termination complete"); 335 // and now terminate forcefully 336 if (exit) { 337 System.exit(status); 338 } 339 } 340 } 341 342 // blocks the main Thread until tasks complete or timed out 343 private void runShutDownTasks(Set<Runnable> toRun, String threadName ) { 344 Set<Runnable> sDrunnables = new HashSet<>(toRun); // copy list so cannot be modified 345 if ( sDrunnables.isEmpty() ) { 346 return; 347 } 348 // use a custom Executor which checks the Task output for Exceptions. 349 ExecutorService executor = new ShutDownThreadPoolExecutor(sDrunnables.size(), threadName); 350 List<Future<?>> complete = new ArrayList<>(); 351 long timeoutEnd = new Date().getTime()+ tasksTimeOutMilliSec; 352 353 354 sDrunnables.forEach((runnable) -> { 355 complete.add(executor.submit(runnable)); 356 }); 357 358 executor.shutdown(); // no more tasks allowed from here, starts the threads. 359 while (!executor.isTerminated() && ( timeoutEnd > new Date().getTime() )) { 360 try { 361 // awaitTermination blocks the thread, checking occasionally 362 executor.awaitTermination(50, TimeUnit.MILLISECONDS); 363 } catch (InterruptedException e) { 364 // ignore 365 } 366 } 367 368 // so we're either all complete or timed out. 369 // check if a task timed out, if so log. 370 for ( Future<?> future : complete) { 371 if ( !future.isDone() ) { 372 log.error("Could not complete Shutdown Task in time: {} ", future ); 373 } 374 } 375 376 executor.shutdownNow(); // do not leave Threads hanging before exit, force stop. 377 378 } 379 380 /** 381 * {@inheritDoc} 382 */ 383 @Override 384 public boolean isShuttingDown() { 385 return shuttingDown; 386 } 387 388 /** 389 * This method is static so that if multiple DefaultShutDownManagers are 390 * registered, they are all aware of this state. 391 * 392 * @param state true if shutting down; false otherwise 393 */ 394 protected void setShuttingDown(boolean state) { 395 boolean old = shuttingDown; 396 setStaticShuttingDown(state); 397 log.debug("Setting shuttingDown to {}", state); 398 firePropertyChange(PROP_SHUTTING_DOWN, old, state); 399 } 400 401 // package private so tests can reset 402 synchronized static void setStaticShuttingDown(boolean state){ 403 shuttingDown = state; 404 } 405 406 private static class ShutDownThreadPoolExecutor extends ThreadPoolExecutor { 407 408 // use up to 8 threads for parallel tasks 409 // 10 seconds for tasks to enter a thread from queue 410 // set thread name with custom ThreadFactory 411 private ShutDownThreadPoolExecutor(int numberTasks, String threadName) { 412 super(8, 8, 10, TimeUnit.SECONDS, 413 new ArrayBlockingQueue<Runnable>(numberTasks), new ShutDownThreadFactory(threadName)); 414 } 415 416 @Override 417 public void afterExecute(Runnable r, Throwable t) { 418 super.afterExecute(r, t); 419 // System.out.println("afterExecute "+ r); 420 if (t == null && r instanceof Future<?>) { 421 try { 422 Future<?> future = (Future<?>) r; 423 if (future.isDone()) { 424 future.get(); 425 } 426 } catch (CancellationException ce) { 427 t = ce; 428 } catch (ExecutionException ee) { 429 t = ee.getCause(); 430 } catch (InterruptedException ie) { 431 Thread.currentThread().interrupt(); 432 } 433 } 434 if (t != null) { 435 log.error("Issue Completing ShutdownTask : ", t); 436 } 437 } 438 } 439 440 private static class ShutDownThreadFactory implements ThreadFactory { 441 442 private final String threadName; 443 444 private ShutDownThreadFactory( String threadName ){ 445 super(); 446 this.threadName = threadName; 447 } 448 449 @Override 450 public Thread newThread(Runnable r) { 451 return new Thread(r, threadName); 452 } 453 } 454 455 private static class EarlyTask implements Runnable { 456 457 final ShutDownTask task; // access outside of this class 458 459 EarlyTask( ShutDownTask runnableTask) { 460 task = runnableTask; 461 } 462 463 @Override 464 public void run() { 465 task.runEarly(); 466 } 467 468 @Override // improve error message on failure 469 public String toString(){ 470 return task.toString(); 471 } 472 473 } 474 475 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultShutDownManager.class); 476 477}