001package jmri.util; 002 003import java.lang.reflect.InvocationTargetException; 004import java.util.TimerTask; 005 006import javax.swing.SwingUtilities; 007import javax.annotation.Nonnull; 008import javax.annotation.concurrent.ThreadSafe; 009 010import jmri.JmriException; 011import jmri.Reference; 012 013/** 014 * Utilities for handling JMRI's threading conventions. 015 * <p> 016 * For background, see 017 * <a href="http://jmri.org/help/en/html/doc/Technical/Threads.shtml">http://jmri.org/help/en/html/doc/Technical/Threads.shtml</a> 018 * <p> 019 * Note this distinguishes "on layout", for example, Setting a sensor, from "on 020 * GUI", for example, manipulating the Swing GUI. That may not be an important 021 * distinction now, but it might be later, so we build it into the calls. 022 * 023 * @author Bob Jacobsen Copyright 2015 024 */ 025@ThreadSafe 026public class ThreadingUtil { 027 028 /** 029 * Run some layout-specific code before returning. 030 * <p> 031 * Typical uses: 032 * <p> {@code 033 * ThreadingUtil.runOnLayout(() -> { 034 * sensor.setState(value); 035 * }); 036 * } 037 * 038 * @param ta What to run, usually as a lambda expression 039 */ 040 public static void runOnLayout(@Nonnull ThreadAction ta) { 041 runOnGUI(ta); 042 } 043 044 /** 045 * Run some layout-specific code before returning. 046 * This method catches and rethrows JmriException and RuntimeException. 047 * <p> 048 * Typical uses: 049 * <p> {@code 050 * ThreadingUtil.runOnLayout(() -> { 051 * sensor.setState(value); 052 * }); 053 * } 054 * 055 * @param ta What to run, usually as a lambda expression 056 * @throws JmriException when an exception occurs 057 * @throws RuntimeException when an exception occurs 058 */ 059 public static void runOnLayoutWithJmriException( 060 @Nonnull ThreadActionWithJmriException ta) 061 throws JmriException, RuntimeException { 062 runOnGUIWithJmriException(ta); 063 } 064 065 /** 066 * Run some layout-specific code at some later point. 067 * <p> 068 * Please note the operation may have happened before this returns. Or 069 * later. No long-term guarantees. 070 * <p> 071 * Typical uses: 072 * <p> {@code 073 * ThreadingUtil.runOnLayoutEventually(() -> { 074 * sensor.setState(value); 075 * }); 076 * } 077 * 078 * @param ta What to run, usually as a lambda expression 079 */ 080 public static void runOnLayoutEventually(@Nonnull ThreadAction ta) { 081 runOnGUIEventually(ta); 082 } 083 084 /** 085 * Run some layout-specific code at some later point, at least a known time 086 * in the future. 087 * <p> 088 * There is no long-term guarantee about the accuracy of the interval. 089 * <p> 090 * Typical uses: 091 * <p> {@code 092 * ThreadingUtil.runOnLayoutDelayed(() -> { 093 * sensor.setState(value); 094 * }, 1000); 095 * } 096 * 097 * If you need to cancel an operation, see jmri.util.TimerUtil.scheduleOnGUIThread 098 * 099 * @param ta what to run, usually as a lambda expression 100 * @param delay interval in milliseconds 101 */ 102 public static void runOnLayoutDelayed(@Nonnull ThreadAction ta, int delay) { 103 runOnGUIDelayed(ta, delay); 104 } 105 106 /** 107 * Check if on the layout-operation thread. 108 * 109 * @return true if on the layout-operation thread 110 */ 111 public static boolean isLayoutThread() { 112 return isGUIThread(); 113 } 114 115 /** 116 * Run some GUI-specific code before returning 117 * <p> 118 * Typical uses: 119 * <p> {@code 120 * ThreadingUtil.runOnGUI(() -> { 121 * mine.setVisible(); 122 * }); 123 * } 124 * <p> 125 * If an InterruptedException is encountered, it'll be deferred to the 126 * next blocking call via Thread.currentThread().interrupt() 127 * 128 * @param ta What to run, usually as a lambda expression 129 */ 130 public static void runOnGUI(@Nonnull ThreadAction ta) { 131 if (isGUIThread()) { 132 // run now 133 ta.run(); 134 } else { 135 // dispatch to Swing 136 warnLocks(); 137 try { 138 SwingUtilities.invokeAndWait(ta); 139 } catch (InterruptedException e) { 140 log.debug("Interrupted while running on GUI thread"); 141 Thread.currentThread().interrupt(); 142 } catch (InvocationTargetException e) { 143 log.error("Error while on GUI thread", e.getCause()); 144 log.error(" Came from call to runOnGUI:", e); 145 // should have been handled inside the ThreadAction 146 } 147 } 148 } 149 150 /** 151 * Run some GUI-specific code before returning. 152 * This method catches and rethrows JmriException and RuntimeException. 153 * <p> 154 * Typical uses: 155 * <p> {@code 156 * ThreadingUtil.runOnGUI(() -> { 157 * mine.setVisible(); 158 * }); 159 * } 160 * <p> 161 * If an InterruptedException is encountered, it'll be deferred to the 162 * next blocking call via Thread.currentThread().interrupt() 163 * 164 * @param ta What to run, usually as a lambda expression 165 * @throws JmriException when an exception occurs 166 * @throws RuntimeException when an exception occurs 167 */ 168 public static void runOnGUIWithJmriException( 169 @Nonnull ThreadActionWithJmriException ta) 170 throws JmriException, RuntimeException { 171 172 if (isGUIThread()) { 173 // run now 174 ta.run(); 175 } else { 176 // dispatch to Swing 177 warnLocks(); 178 try { 179 Reference<JmriException> jmriException = new Reference<>(); 180 Reference<RuntimeException> runtimeException = new Reference<>(); 181 SwingUtilities.invokeAndWait(() -> { 182 try { 183 ta.run(); 184 } catch (JmriException e) { 185 jmriException.set(e); 186 } catch (RuntimeException e) { 187 runtimeException.set(e); 188 } 189 }); 190 JmriException je = jmriException.get(); 191 if (je != null) throw je; 192 RuntimeException re = runtimeException.get(); 193 if (re != null) throw re; 194 } catch (InterruptedException e) { 195 log.debug("Interrupted while running on GUI thread"); 196 Thread.currentThread().interrupt(); 197 } catch (InvocationTargetException e) { 198 log.error("Error while on GUI thread", e.getCause()); 199 log.error(" Came from call to runOnGUI:", e); 200 // should have been handled inside the ThreadAction 201 } 202 } 203 } 204 205 /** 206 * Run some GUI-specific code before returning a value. 207 * <p> 208 * Typical uses: 209 * <p> 210 * {@code 211 * Boolean retval = ThreadingUtil.runOnGUIwithReturn(() -> { 212 * return mine.isVisible(); 213 * }); 214 * } 215 * <p> 216 * If an InterruptedException is encountered, it'll be deferred to the next 217 * blocking call via Thread.currentThread().interrupt() 218 * 219 * @param <E> generic 220 * @param ta What to run, usually as a lambda expression 221 * @return the value returned by ta 222 */ 223 public static <E> E runOnGUIwithReturn(@Nonnull ReturningThreadAction<E> ta) { 224 if (isGUIThread()) { 225 // run now 226 return ta.run(); 227 } else { 228 warnLocks(); 229 // dispatch to Swing 230 final Reference<E> result = new Reference<>(); 231 try { 232 SwingUtilities.invokeAndWait(() -> { 233 result.set(ta.run()); 234 }); 235 } catch (InterruptedException e) { 236 log.debug("Interrupted while running on GUI thread"); 237 Thread.currentThread().interrupt(); 238 } catch (InvocationTargetException e) { 239 log.error("Error while on GUI thread", e.getCause()); 240 log.error(" Came from call to runOnGUIwithReturn:", e); 241 // should have been handled inside the ThreadAction 242 } 243 return result.get(); 244 } 245 } 246 247 /** 248 * Run some GUI-specific code at some later point. 249 * <p> 250 * If invoked from the GUI thread, the work is guaranteed to happen only 251 * after the current routine has returned. 252 * <p> 253 * Typical uses: 254 * <p> {@code 255 * ThreadingUtil.runOnGUIEventually( ()->{ 256 * mine.setVisible(); 257 * } ); 258 * } 259 * 260 * @param ta What to run, usually as a lambda expression 261 */ 262 public static void runOnGUIEventually(@Nonnull ThreadAction ta) { 263 // dispatch to Swing 264 SwingUtilities.invokeLater(ta); 265 } 266 267 /** 268 * Run some GUI-specific code at some later point, at least a known time in 269 * the future. 270 * <p> 271 * There is no long-term guarantee about the accuracy of the interval. 272 * <p> 273 * Typical uses: 274 * <p> 275 * {@code 276 * ThreadingUtil.runOnGUIDelayed( ()->{ 277 * mine.setVisible(); 278 * }, 1000); 279 * } 280 * 281 * If you need to cancel an operation, see jmri.util.TimerUtil.scheduleOnGUIThread 282 * 283 * @param ta What to run, usually as a lambda expression 284 * @param delay interval in milliseconds 285 */ 286 public static void runOnGUIDelayed(@Nonnull Runnable ta, int delay) { 287 // dispatch to Swing via timer 288 var task = new TimerTask(){ 289 @Override 290 public void run() { 291 ThreadingUtil.runOnGUIEventually(() -> {ta.run();}); 292 } 293 }; 294 TimerUtil.scheduleOnGUIThread(task, delay); 295 } 296 297 /** 298 * Check if on the GUI event dispatch thread. 299 * 300 * @return true if on the event dispatch thread 301 */ 302 public static 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 public static 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 public static 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 public static 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 public static 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 public static 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 public static 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 public static 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 public static 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 public static 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 public static 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 public static 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 static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ThreadingUtil.class); 490 491} 492