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