/
ThreadingUtil.java
350 lines (325 loc) · 12.1 KB
/
ThreadingUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
package jmri.util;
import java.awt.event.ActionEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.ThreadSafe;
/**
* Utilities for handling JMRI's threading conventions.
* <p>
* For background, see
* <a href="http://jmri.org/help/en/html/doc/Technical/Threads.shtml">http://jmri.org/help/en/html/doc/Technical/Threads.shtml</a>
* <p>
* Note this distinguishes "on layout", for example, Setting a sensor, from "on
* GUI", for example, manipulating the Swing GUI. That may not be an important
* distinction now, but it might be later, so we build it into the calls.
*
* @author Bob Jacobsen Copyright 2015
*/
@ThreadSafe
public class ThreadingUtil {
/**
* Run some layout-specific code before returning.
* <p>
* Typical uses:
* <p> {@code
* ThreadingUtil.runOnLayout(() -> {
* sensor.setState(value);
* });
* }
*
* @param ta What to run, usually as a lambda expression
*/
static public void runOnLayout(@Nonnull ThreadAction ta) {
runOnGUI(ta);
}
/**
* Run some layout-specific code at some later point.
* <p>
* Please note the operation may have happened before this returns. Or
* later. No long-term guarantees.
* <p>
* Typical uses:
* <p> {@code
* ThreadingUtil.runOnLayoutEventually(() -> {
* sensor.setState(value);
* });
* }
*
* @param ta What to run, usually as a lambda expression
*/
static public void runOnLayoutEventually(@Nonnull ThreadAction ta) {
runOnGUIEventually(ta);
}
/**
* Run some layout-specific code at some later point, at least a known time
* in the future.
* <p>
* There is no long-term guarantee about the accuracy of the interval.
* <p>
* Typical uses:
* <p> {@code
* ThreadingUtil.runOnLayoutDelayed(() -> {
* sensor.setState(value);
* }, 1000);
* }
*
* @param ta what to run, usually as a lambda expression
* @param delay interval in milliseconds
* @return reference to timer object handling delay so you can cancel if desired; note that operation may have already taken place.
*/
@Nonnull
static public Timer runOnLayoutDelayed(@Nonnull ThreadAction ta, int delay) {
return runOnGUIDelayed(ta, delay);
}
/**
* Check if on the layout-operation thread.
*
* @return true if on the layout-operation thread
*/
static public boolean isLayoutThread() {
return isGUIThread();
}
/**
* Run some GUI-specific code before returning
* <p>
* Typical uses:
* <p> {@code
* ThreadingUtil.runOnGUI(() -> {
* mine.setVisible();
* });
* }
* <p>
* If an InterruptedException is encountered, it'll be deferred to the
* next blocking call via Thread.currentThread().interrupt()
*
* @param ta What to run, usually as a lambda expression
*/
static public void runOnGUI(@Nonnull ThreadAction ta) {
if (isGUIThread()) {
// run now
ta.run();
} else {
// dispatch to Swing
warnLocks();
try {
SwingUtilities.invokeAndWait(ta);
} catch (InterruptedException e) {
log.debug("Interrupted while running on GUI thread");
Thread.currentThread().interrupt();
} catch (InvocationTargetException e) {
log.error("Error while on GUI thread", e.getCause());
log.error(" Came from call to runOnGUI:", e);
// should have been handled inside the ThreadAction
}
}
}
/**
* Run some GUI-specific code before returning a value
* <p>
* Typical uses:
* <p>
* {@code
* ThreadingUtil.runOnGUI(() -> {
* mine.setVisible();
* });
* }
* <p>
* If an InterruptedException is encountered, it'll be deferred to the next
* blocking call via Thread.currentThread().interrupt()
*
* @param ta What to run, usually as a lambda expression
* @return the value returned by ta
*/
static public <E> E runOnGUIwithReturn(@Nonnull ReturningThreadAction<E> ta) {
if (isGUIThread()) {
// run now
return ta.run();
} else {
warnLocks();
// dispatch to Swing
final AtomicReference<E> result = new AtomicReference<>();
try {
SwingUtilities.invokeAndWait(() -> {
result.set(ta.run());
});
} catch (InterruptedException e) {
log.debug("Interrupted while running on GUI thread");
Thread.currentThread().interrupt();
} catch (InvocationTargetException e) {
log.error("Error while on GUI thread", e.getCause());
log.error(" Came from call to runOnGUIwithReturn:", e);
// should have been handled inside the ThreadAction
}
return result.get();
}
}
/**
* Run some GUI-specific code at some later point.
* <p>
* If invoked from the GUI thread, the work is guaranteed to happen only
* after the current routine has returned.
* <p>
* Typical uses:
* <p> {@code
* ThreadingUtil.runOnGUIEventually( ()->{
* mine.setVisible();
* } );
* }
*
* @param ta What to run, usually as a lambda expression
*/
static public void runOnGUIEventually(@Nonnull ThreadAction ta) {
// dispatch to Swing
SwingUtilities.invokeLater(ta);
}
/**
* Run some GUI-specific code at some later point, at least a known time in
* the future.
* <p>
* There is no long-term guarantee about the accuracy of the interval.
* <p>
* Typical uses:
* <p>
* {@code
* ThreadingUtil.runOnGUIEventually( ()->{
* mine.setVisible();
* }, 1000);
* }
*
* @param ta What to run, usually as a lambda expression
* @param delay interval in milliseconds
* @return reference to timer object handling delay so you can cancel if desired; note that operation may have already taken place.
*/
@Nonnull
static public Timer runOnGUIDelayed(@Nonnull ThreadAction ta, int delay) {
// dispatch to Swing via timer
Timer timer = new Timer(delay, (ActionEvent e) -> {
ta.run();
});
timer.setRepeats(false);
timer.start();
return timer;
}
/**
* Check if on the GUI event dispatch thread.
*
* @return true if on the event dispatch thread
*/
static public boolean isGUIThread() {
return SwingUtilities.isEventDispatchThread();
}
/**
* Check whether a specific thread is running (or able to run) right now.
*
* @param t the thread to check
* @return true is the specified thread is or could be running right now
*/
static public boolean canThreadRun(@Nonnull Thread t) {
Thread.State s = t.getState();
return s.equals(Thread.State.RUNNABLE);
}
/**
* Check whether a specific thread is currently waiting.
* <p>
* Note: This includes both waiting due to an explicit wait() call, and due
* to being blocked attempting to synchronize.
* <p>
* Note: {@link #canThreadRun(Thread)} and {@link #isThreadWaiting(Thread)}
* should never simultaneously be true, but it might look that way due to
* sampling delays when checking on another thread.
*
* @param t the thread to check
* @return true is the specified thread is or could be running right now
*/
static public boolean isThreadWaiting(@Nonnull Thread t) {
Thread.State s = t.getState();
return s.equals(Thread.State.BLOCKED) || s.equals(Thread.State.WAITING) || s.equals(Thread.State.TIMED_WAITING);
}
/**
* Check that a call is on the GUI thread. Warns (once) if not.
* Intended to be the run-time check mechanism for {@code @InvokeOnGuiThread}
* <p>
* In this implementation, this is the same as {@link #requireLayoutThread(org.slf4j.Logger)}
* @param logger The logger object from the calling class, usually "log"
*/
static public void requireGuiThread(org.slf4j.Logger logger) {
if (!isGUIThread()) {
// fail, which can be a bit slow to do the right thing
Log4JUtil.warnOnce(logger, "Call not on GUI thread", new Exception("traceback"));
}
}
/**
* Check that a call is on the Layout thread. Warns (once) if not.
* Intended to be the run-time check mechanism for {@code @InvokeOnLayoutThread}
* <p>
* In this implementation, this is the same as {@link #requireGuiThread(org.slf4j.Logger)}
* @param logger The logger object from the calling class, usually "log"
*/
static public void requireLayoutThread(org.slf4j.Logger logger) {
if (!isLayoutThread()) {
// fail, which can be a bit slow to do the right thing
Log4JUtil.warnOnce(logger, "Call not on Layout thread", new Exception("traceback"));
}
}
/**
* Interface for use in ThreadingUtil's lambda interfaces
*/
@FunctionalInterface
static public interface ThreadAction extends Runnable {
/**
* {@inheritDoc}
* <p>
* Must handle its own exceptions.
*/
@Override
public void run();
}
/**
* Interface for use in ThreadingUtil's lambda interfaces
*
* @param <E> the type returned
*/
@FunctionalInterface
static public interface ReturningThreadAction<E> {
public E run();
}
/**
* Warn if a thread is holding locks. Used when transitioning to another context.
*/
static public void warnLocks() {
if ( log.isDebugEnabled() ) {
try {
java.lang.management.ThreadInfo threadInfo = java.lang.management.ManagementFactory
.getThreadMXBean()
.getThreadInfo(new long[]{Thread.currentThread().getId()}, true, true)[0];
java.lang.management.MonitorInfo[] monitors = threadInfo.getLockedMonitors();
for (java.lang.management.MonitorInfo mon : monitors) {
log.warn("Thread was holding monitor {} from {}", mon, mon.getLockedStackFrame(), Log4JUtil.shortenStacktrace(new Exception("traceback"))); // yes, warn - for re-enable later
}
java.lang.management.LockInfo[] locks = threadInfo.getLockedSynchronizers();
for (java.lang.management.LockInfo lock : locks) {
// certain locks are part of routine Java API operations
if (lock.toString().startsWith("java.util.concurrent.ThreadPoolExecutor$Worker") ) {
log.debug("Thread was holding java lock {}", lock, Log4JUtil.shortenStacktrace(new Exception("traceback"))); // yes, warn - for re-enable later
} else {
log.warn("Thread was holding lock {}", lock, Log4JUtil.shortenStacktrace(new Exception("traceback"))); // yes, warn - for re-enable later
}
}
} catch (RuntimeException ex) {
// just record exceptions for later pick up during debugging
if (!lastWarnLocksLimit) log.warn("Exception in warnLocks", ex);
lastWarnLocksLimit = true;
lastWarnLocksException = ex;
}
}
}
private static boolean lastWarnLocksLimit = false;
private static RuntimeException lastWarnLocksException = null;
public RuntimeException getlastWarnLocksException() { // public for script and test access
return lastWarnLocksException;
}
private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ThreadingUtil.class);
}