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