001package jmri.util; 002 003import java.awt.event.ActionEvent; 004import java.lang.reflect.InvocationTargetException; 005 006import javax.swing.SwingUtilities; 007import javax.swing.Timer; 008import javax.annotation.Nonnull; 009import javax.annotation.concurrent.ThreadSafe; 010 011import jmri.JmriException; 012import jmri.Reference; 013 014/** 015 * Utilities for handling JMRI's threading conventions. 016 * <p> 017 * For background, see 018 * <a href="http://jmri.org/help/en/html/doc/Technical/Threads.shtml">http://jmri.org/help/en/html/doc/Technical/Threads.shtml</a> 019 * <p> 020 * Note this distinguishes "on layout", for example, Setting a sensor, from "on 021 * GUI", for example, manipulating the Swing GUI. That may not be an important 022 * distinction now, but it might be later, so we build it into the calls. 023 * 024 * @author Bob Jacobsen Copyright 2015 025 */ 026@ThreadSafe 027public class ThreadingUtil { 028 029 /** 030 * Run some layout-specific code before returning. 031 * <p> 032 * Typical uses: 033 * <p> {@code 034 * ThreadingUtil.runOnLayout(() -> { 035 * sensor.setState(value); 036 * }); 037 * } 038 * 039 * @param ta What to run, usually as a lambda expression 040 */ 041 static public void runOnLayout(@Nonnull ThreadAction ta) { 042 runOnGUI(ta); 043 } 044 045 /** 046 * Run some layout-specific code before returning. 047 * This method catches and rethrows JmriException and RuntimeException. 048 * <p> 049 * Typical uses: 050 * <p> {@code 051 * ThreadingUtil.runOnLayout(() -> { 052 * sensor.setState(value); 053 * }); 054 * } 055 * 056 * @param ta What to run, usually as a lambda expression 057 * @throws JmriException when an exception occurs 058 * @throws RuntimeException when an exception occurs 059 */ 060 static public void runOnLayoutWithJmriException( 061 @Nonnull ThreadActionWithJmriException ta) 062 throws JmriException, RuntimeException { 063 runOnGUIWithJmriException(ta); 064 } 065 066 /** 067 * Run some layout-specific code at some later point. 068 * <p> 069 * Please note the operation may have happened before this returns. Or 070 * later. No long-term guarantees. 071 * <p> 072 * Typical uses: 073 * <p> {@code 074 * ThreadingUtil.runOnLayoutEventually(() -> { 075 * sensor.setState(value); 076 * }); 077 * } 078 * 079 * @param ta What to run, usually as a lambda expression 080 */ 081 static public void runOnLayoutEventually(@Nonnull ThreadAction ta) { 082 runOnGUIEventually(ta); 083 } 084 085 /** 086 * Run some layout-specific code at some later point, at least a known time 087 * in the future. 088 * <p> 089 * There is no long-term guarantee about the accuracy of the interval. 090 * <p> 091 * Typical uses: 092 * <p> {@code 093 * ThreadingUtil.runOnLayoutDelayed(() -> { 094 * sensor.setState(value); 095 * }, 1000); 096 * } 097 * 098 * @param ta what to run, usually as a lambda expression 099 * @param delay interval in milliseconds 100 * @return reference to timer object handling delay so you can cancel if desired; note that operation may have already taken place. 101 */ 102 @Nonnull 103 static public Timer runOnLayoutDelayed(@Nonnull ThreadAction ta, int delay) { 104 return runOnGUIDelayed(ta, delay); 105 } 106 107 /** 108 * Check if on the layout-operation thread. 109 * 110 * @return true if on the layout-operation thread 111 */ 112 static public boolean isLayoutThread() { 113 return isGUIThread(); 114 } 115 116 /** 117 * Run some GUI-specific code before returning 118 * <p> 119 * Typical uses: 120 * <p> {@code 121 * ThreadingUtil.runOnGUI(() -> { 122 * mine.setVisible(); 123 * }); 124 * } 125 * <p> 126 * If an InterruptedException is encountered, it'll be deferred to the 127 * next blocking call via Thread.currentThread().interrupt() 128 * 129 * @param ta What to run, usually as a lambda expression 130 */ 131 static public void runOnGUI(@Nonnull ThreadAction ta) { 132 if (isGUIThread()) { 133 // run now 134 ta.run(); 135 } else { 136 // dispatch to Swing 137 warnLocks(); 138 try { 139 SwingUtilities.invokeAndWait(ta); 140 } catch (InterruptedException e) { 141 log.debug("Interrupted while running on GUI thread"); 142 Thread.currentThread().interrupt(); 143 } catch (InvocationTargetException e) { 144 log.error("Error while on GUI thread", e.getCause()); 145 log.error(" Came from call to runOnGUI:", e); 146 // should have been handled inside the ThreadAction 147 } 148 } 149 } 150 151 /** 152 * Run some GUI-specific code before returning. 153 * This method catches and rethrows JmriException and RuntimeException. 154 * <p> 155 * Typical uses: 156 * <p> {@code 157 * ThreadingUtil.runOnGUI(() -> { 158 * mine.setVisible(); 159 * }); 160 * } 161 * <p> 162 * If an InterruptedException is encountered, it'll be deferred to the 163 * next blocking call via Thread.currentThread().interrupt() 164 * 165 * @param ta What to run, usually as a lambda expression 166 * @throws JmriException when an exception occurs 167 * @throws RuntimeException when an exception occurs 168 */ 169 static public void runOnGUIWithJmriException( 170 @Nonnull ThreadActionWithJmriException ta) 171 throws JmriException, RuntimeException { 172 173 if (isGUIThread()) { 174 // run now 175 ta.run(); 176 } else { 177 // dispatch to Swing 178 warnLocks(); 179 try { 180 Reference<JmriException> jmriException = new Reference<>(); 181 Reference<RuntimeException> runtimeException = new Reference<>(); 182 SwingUtilities.invokeAndWait(() -> { 183 try { 184 ta.run(); 185 } catch (JmriException e) { 186 jmriException.set(e); 187 } catch (RuntimeException e) { 188 runtimeException.set(e); 189 } 190 }); 191 JmriException je = jmriException.get(); 192 if (je != null) throw je; 193 RuntimeException re = runtimeException.get(); 194 if (re != null) throw re; 195 } catch (InterruptedException e) { 196 log.debug("Interrupted while running on GUI thread"); 197 Thread.currentThread().interrupt(); 198 } catch (InvocationTargetException e) { 199 log.error("Error while on GUI thread", e.getCause()); 200 log.error(" Came from call to runOnGUI:", e); 201 // should have been handled inside the ThreadAction 202 } 203 } 204 } 205 206 /** 207 * Run some GUI-specific code before returning a value. 208 * <p> 209 * Typical uses: 210 * <p> 211 * {@code 212 * Boolean retval = ThreadingUtil.runOnGUIwithReturn(() -> { 213 * return mine.isVisible(); 214 * }); 215 * } 216 * <p> 217 * If an InterruptedException is encountered, it'll be deferred to the next 218 * blocking call via Thread.currentThread().interrupt() 219 * 220 * @param <E> generic 221 * @param ta What to run, usually as a lambda expression 222 * @return the value returned by ta 223 */ 224 static public <E> E runOnGUIwithReturn(@Nonnull ReturningThreadAction<E> ta) { 225 if (isGUIThread()) { 226 // run now 227 return ta.run(); 228 } else { 229 warnLocks(); 230 // dispatch to Swing 231 final Reference<E> result = new Reference<>(); 232 try { 233 SwingUtilities.invokeAndWait(() -> { 234 result.set(ta.run()); 235 }); 236 } catch (InterruptedException e) { 237 log.debug("Interrupted while running on GUI thread"); 238 Thread.currentThread().interrupt(); 239 } catch (InvocationTargetException e) { 240 log.error("Error while on GUI thread", e.getCause()); 241 log.error(" Came from call to runOnGUIwithReturn:", e); 242 // should have been handled inside the ThreadAction 243 } 244 return result.get(); 245 } 246 } 247 248 /** 249 * Run some GUI-specific code at some later point. 250 * <p> 251 * If invoked from the GUI thread, the work is guaranteed to happen only 252 * after the current routine has returned. 253 * <p> 254 * Typical uses: 255 * <p> {@code 256 * ThreadingUtil.runOnGUIEventually( ()->{ 257 * mine.setVisible(); 258 * } ); 259 * } 260 * 261 * @param ta What to run, usually as a lambda expression 262 */ 263 static public void runOnGUIEventually(@Nonnull ThreadAction ta) { 264 // dispatch to Swing 265 SwingUtilities.invokeLater(ta); 266 } 267 268 /** 269 * Run some GUI-specific code at some later point, at least a known time in 270 * the future. 271 * <p> 272 * There is no long-term guarantee about the accuracy of the interval. 273 * <p> 274 * Typical uses: 275 * <p> 276 * {@code 277 * ThreadingUtil.runOnGUIDelayed( ()->{ 278 * mine.setVisible(); 279 * }, 1000); 280 * } 281 * 282 * @param ta What to run, usually as a lambda expression 283 * @param delay interval in milliseconds 284 * @return reference to timer object handling delay so you can cancel if desired; note that operation may have already taken place. 285 */ 286 @Nonnull 287 static public Timer runOnGUIDelayed(@Nonnull ThreadAction ta, int delay) { 288 // dispatch to Swing via timer 289 Timer timer = new Timer(delay, (ActionEvent e) -> { 290 ta.run(); 291 }); 292 timer.setRepeats(false); 293 timer.start(); 294 return timer; 295 } 296 297 /** 298 * Check if on the GUI event dispatch thread. 299 * 300 * @return true if on the event dispatch thread 301 */ 302 static public boolean isGUIThread() { 303 return SwingUtilities.isEventDispatchThread(); 304 } 305 306 /** 307 * Create a new thread in the JMRI group 308 * @param runner Runnable. 309 * @return new Thread. 310 */ 311 static public Thread newThread(Runnable runner) { 312 return new Thread(getJmriThreadGroup(), runner); 313 } 314 315 /** 316 * Create a new thread in the JMRI group. 317 * @param runner Thread runnable. 318 * @param name Thread name. 319 * @return New Thread. 320 */ 321 static public Thread newThread(Runnable runner, String name) { 322 return new Thread(getJmriThreadGroup(), runner, name); 323 } 324 325 /** 326 * Get the JMRI default thread group. 327 * This should be passed to as the first argument to the {@link Thread} 328 * constructor so we can track JMRI-created threads. 329 * @return JMRI default thread group. 330 */ 331 static public ThreadGroup getJmriThreadGroup() { 332 // we access this dynamically instead of keeping it in a static 333 334 ThreadGroup main = Thread.currentThread().getThreadGroup(); 335 while (main.getParent() != null ) {main = main.getParent(); } 336 ThreadGroup[] list = new ThreadGroup[main.activeGroupCount()+2]; // space on end 337 int max = main.enumerate(list); 338 339 for (int i = 0; i<max; i++) { // usually just 2 or 3, quite quick 340 if (list[i].getName().equals("JMRI")) return list[i]; 341 } 342 return new ThreadGroup(main, "JMRI"); 343 } 344 345 /** 346 * Check whether a specific thread is running (or able to run) right now. 347 * 348 * @param t the thread to check 349 * @return true is the specified thread is or could be running right now 350 */ 351 static public boolean canThreadRun(@Nonnull Thread t) { 352 Thread.State s = t.getState(); 353 return s.equals(Thread.State.RUNNABLE); 354 } 355 356 /** 357 * Check whether a specific thread is currently waiting. 358 * <p> 359 * Note: This includes both waiting due to an explicit wait() call, and due 360 * to being blocked attempting to synchronize. 361 * <p> 362 * Note: {@link #canThreadRun(Thread)} and {@link #isThreadWaiting(Thread)} 363 * should never simultaneously be true, but it might look that way due to 364 * sampling delays when checking on another thread. 365 * 366 * @param t the thread to check 367 * @return true is the specified thread is or could be running right now 368 */ 369 static public boolean isThreadWaiting(@Nonnull Thread t) { 370 Thread.State s = t.getState(); 371 return s.equals(Thread.State.BLOCKED) || s.equals(Thread.State.WAITING) || s.equals(Thread.State.TIMED_WAITING); 372 } 373 374 /** 375 * Check that a call is on the GUI thread. Warns (once) if not. 376 * Intended to be the run-time check mechanism for {@code @InvokeOnGuiThread} 377 * <p> 378 * In this implementation, this is the same as {@link #requireLayoutThread(org.slf4j.Logger)} 379 * @param logger The logger object from the calling class, usually "log" 380 */ 381 static public void requireGuiThread(org.slf4j.Logger logger) { 382 if (!isGUIThread()) { 383 // fail, which can be a bit slow to do the right thing 384 LoggingUtil.warnOnce(logger, "Call not on GUI thread", new Exception("traceback")); 385 } 386 } 387 388 /** 389 * Check that a call is on the Layout thread. Warns (once) if not. 390 * Intended to be the run-time check mechanism for {@code @InvokeOnLayoutThread} 391 * <p> 392 * In this implementation, this is the same as {@link #requireGuiThread(org.slf4j.Logger)} 393 * @param logger The logger object from the calling class, usually "log" 394 */ 395 static public void requireLayoutThread(org.slf4j.Logger logger) { 396 if (!isLayoutThread()) { 397 // fail, which can be a bit slow to do the right thing 398 LoggingUtil.warnOnce(logger, "Call not on Layout thread", new Exception("traceback")); 399 } 400 } 401 402 /** 403 * Interface for use in ThreadingUtil's lambda interfaces 404 */ 405 @FunctionalInterface 406 static public interface ThreadAction extends Runnable { 407 408 /** 409 * {@inheritDoc} 410 * <p> 411 * Must handle its own exceptions. 412 */ 413 @Override 414 public void run(); 415 } 416 417 /** 418 * Interface for use in ThreadingUtil's lambda interfaces 419 */ 420 @FunctionalInterface 421 static public interface ThreadActionWithJmriException { 422 423 /** 424 * When an object implementing interface <code>ThreadActionWithJmriException</code> 425 * is used to create a thread, starting the thread causes the object's 426 * <code>run</code> method to be called in that separately executing 427 * thread. 428 * <p> 429 * The general contract of the method <code>run</code> is that it may 430 * take any action whatsoever. 431 * 432 * @throws JmriException when an exception occurs 433 * @throws RuntimeException when an exception occurs 434 * @see java.lang.Thread#run() 435 */ 436 public void run() throws JmriException, RuntimeException; 437 } 438 439 /** 440 * Interface for use in ThreadingUtil's lambda interfaces 441 * 442 * @param <E> the type returned 443 */ 444 @FunctionalInterface 445 static public interface ReturningThreadAction<E> { 446 public E run(); 447 } 448 449 /** 450 * Warn if a thread is holding locks. Used when transitioning to another context. 451 */ 452 @SuppressWarnings("deprecation") // The method getId() from the type Thread is deprecated since version 19 453 // The replacement Thread.threadId() isn't available before version 19 454 static public void warnLocks() { 455 if ( log.isDebugEnabled() ) { 456 try { 457 java.lang.management.ThreadInfo threadInfo = java.lang.management.ManagementFactory 458 .getThreadMXBean() 459 .getThreadInfo(new long[]{Thread.currentThread().getId()}, true, true)[0]; 460 461 java.lang.management.MonitorInfo[] monitors = threadInfo.getLockedMonitors(); 462 for (java.lang.management.MonitorInfo mon : monitors) { 463 log.warn("Thread was holding monitor {} from {}", mon, mon.getLockedStackFrame(), LoggingUtil.shortenStacktrace(new Exception("traceback"))); // yes, warn - for re-enable later 464 } 465 466 java.lang.management.LockInfo[] locks = threadInfo.getLockedSynchronizers(); 467 for (java.lang.management.LockInfo lock : locks) { 468 // certain locks are part of routine Java API operations 469 if (lock.toString().startsWith("java.util.concurrent.ThreadPoolExecutor$Worker") ) { 470 log.debug("Thread was holding java lock {}", lock, LoggingUtil.shortenStacktrace(new Exception("traceback"))); // yes, warn - for re-enable later 471 } else { 472 log.warn("Thread was holding lock {}", lock, LoggingUtil.shortenStacktrace(new Exception("traceback"))); // yes, warn - for re-enable later 473 } 474 } 475 } catch (RuntimeException ex) { 476 // just record exceptions for later pick up during debugging 477 if (!lastWarnLocksLimit) log.warn("Exception in warnLocks", ex); 478 lastWarnLocksLimit = true; 479 lastWarnLocksException = ex; 480 } 481 } 482 } 483 private static boolean lastWarnLocksLimit = false; 484 private static RuntimeException lastWarnLocksException = null; 485 public RuntimeException getlastWarnLocksException() { // public for script and test access 486 return lastWarnLocksException; 487 } 488 489 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ThreadingUtil.class); 490 491} 492