-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#463: Add ability for timeout to print full thread stack dump #768
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,12 @@ | ||
package org.junit.internal.runners.statements; | ||
|
||
import java.lang.management.ManagementFactory; | ||
import java.lang.management.ThreadInfo; | ||
import java.lang.management.ThreadMXBean; | ||
import java.util.Arrays; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Map.Entry; | ||
import java.util.concurrent.Callable; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.FutureTask; | ||
|
@@ -17,21 +21,23 @@ public class FailOnTimeout extends Statement { | |
private final TimeUnit fTimeUnit; | ||
private final long fTimeout; | ||
private final boolean fLookForStuckThread; | ||
private final boolean fFullThreadStackDump; | ||
private ThreadGroup fThreadGroup = null; | ||
|
||
public FailOnTimeout(Statement originalStatement, long millis) { | ||
this(originalStatement, millis, TimeUnit.MILLISECONDS); | ||
} | ||
|
||
public FailOnTimeout(Statement originalStatement, long timeout, TimeUnit unit) { | ||
this(originalStatement, timeout, unit, false); | ||
this(originalStatement, timeout, unit, false, false); | ||
} | ||
|
||
public FailOnTimeout(Statement originalStatement, long timeout, TimeUnit unit, boolean lookForStuckThread) { | ||
public FailOnTimeout(Statement originalStatement, long timeout, TimeUnit unit, boolean lookForStuckThread, boolean fullThreadStackDump) { | ||
fOriginalStatement = originalStatement; | ||
fTimeout = timeout; | ||
fTimeUnit = unit; | ||
fLookForStuckThread = lookForStuckThread; | ||
fFullThreadStackDump = fullThreadStackDump; | ||
} | ||
|
||
@Override | ||
|
@@ -74,18 +80,89 @@ private Exception createTimeoutException(Thread thread) { | |
currThreadException.setStackTrace(stackTrace); | ||
thread.interrupt(); | ||
} | ||
|
||
List<Throwable> exceptions = new ArrayList<Throwable>(); | ||
exceptions.add(currThreadException); | ||
|
||
if (stuckThread != null) { | ||
Exception stuckThreadException = | ||
Exception stuckThreadException = | ||
new Exception ("Appears to be stuck in thread " + | ||
stuckThread.getName()); | ||
stuckThreadException.setStackTrace(getStackTrace(stuckThread)); | ||
return new MultipleFailureException | ||
(Arrays.<Throwable>asList(currThreadException, stuckThreadException)); | ||
exceptions.add(stuckThreadException); | ||
} | ||
|
||
// TODO: would it make sense to make this optional, i.e. if stuckThread != null, then skip the full thread dump? | ||
if (fFullThreadStackDump) { | ||
// For the sake of convenience just add the full thread dump directly to the failure message: this can really | ||
// hurt when getting long :-( | ||
Exception fullThreadDumpException = new Exception( | ||
"Appears to be stuck => Full thread dump:\n" | ||
+ getFullThreadDump()); | ||
exceptions.add(fullThreadDumpException); | ||
} | ||
|
||
if (exceptions.size() > 1) { | ||
return new MultipleFailureException(exceptions); | ||
} else { | ||
return currThreadException; | ||
} | ||
} | ||
|
||
private String getFullThreadDump() { | ||
StringBuilder sb = new StringBuilder(); | ||
|
||
// TODO: ThreadMXBean provides interesting thread dump information (locks, monitors, synchronizers) only with Java >= 1.6 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style-nit: this should line up with the next comment |
||
|
||
// First try ThreadMXBean#findMonitorDeadlockedThreads(): | ||
ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean(); | ||
long[] deadlockedThreadIds = threadMxBean.findMonitorDeadlockedThreads(); | ||
if (deadlockedThreadIds != null) { | ||
sb.append("Found deadlocked threads:"); | ||
ThreadInfo[] threadInfos = threadMxBean.getThreadInfo(deadlockedThreadIds); | ||
for (ThreadInfo threadInfo : threadInfos) { | ||
sb.append("\n\t" + threadInfo.getThreadName() + " Id=" + threadInfo.getThreadId() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: String format would make this code easy to read. |
||
+ " Lock name=" + threadInfo.getLockName() + " Lock owner Id=" + threadInfo.getLockOwnerId() | ||
+ " Lock owner name=" + threadInfo.getLockOwnerName()); | ||
} | ||
} | ||
|
||
// Then just the full thread dump: | ||
Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces(); | ||
sb.append("Thread dump (total threads=" + allStackTraces.size() + ")"); | ||
for (Thread thread : allStackTraces.keySet()) { | ||
sb.append("\n\t" + thread.getName()); | ||
} | ||
sb.append("\n"); | ||
for (Entry<Thread, StackTraceElement[]> threadEntry : allStackTraces.entrySet()) { | ||
sb.append("\n" + threadToHeaderString(threadEntry.getKey())); | ||
|
||
StackTraceElement[] stackTraces = threadEntry.getValue(); | ||
for (int i = 0; i < stackTraces.length; i++) { | ||
StackTraceElement ste = stackTraces[i]; | ||
sb.append("\tat " + ste.toString()); | ||
sb.append('\n'); | ||
} | ||
} | ||
|
||
return sb.toString(); | ||
} | ||
|
||
private String threadToHeaderString(Thread thread) { | ||
StringBuilder sb = new StringBuilder("\"" + thread.getName() + "\"" | ||
+ " Id=" + thread.getId() + " Daemon=" + thread.isDaemon() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: consider using String.format() |
||
+ " State=" + thread.getState() + " Priority=" + thread.getPriority() | ||
+ " Group=" + thread.getThreadGroup().getName()); | ||
if (thread.isAlive()) { | ||
sb.append(" (alive)"); | ||
} | ||
if (thread.isInterrupted()) { | ||
sb.append(" (interrupted)"); | ||
} | ||
sb.append('\n'); | ||
return sb.toString(); | ||
} | ||
|
||
/** | ||
* Retrieves the stack trace for a given thread. | ||
* @param thread The thread whose stack is to be retrieved. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
package org.junit.rules; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
import org.junit.internal.runners.statements.FailOnTimeout; | ||
import org.junit.runner.Description; | ||
import org.junit.runners.model.Statement; | ||
|
||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* The Timeout Rule applies the same timeout to all test methods in a class: | ||
* <pre> | ||
|
@@ -37,6 +37,7 @@ public class Timeout implements TestRule { | |
private final long fTimeout; | ||
private final TimeUnit fTimeUnit; | ||
private boolean fLookForStuckThread; | ||
private boolean fFullThreadStackDump; | ||
|
||
/** | ||
* Create a {@code Timeout} instance with the timeout specified | ||
|
@@ -68,6 +69,7 @@ public Timeout(long timeout, TimeUnit unit) { | |
fTimeout = timeout; | ||
fTimeUnit = unit; | ||
fLookForStuckThread = false; | ||
fFullThreadStackDump = false; | ||
} | ||
|
||
/** | ||
|
@@ -100,7 +102,12 @@ public Timeout lookForStuckThread(boolean enable) { | |
return this; | ||
} | ||
|
||
public Timeout printFullThreadStackDump(boolean enable) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should make this return a new instance, so the fields can be final. To avoid having a large number of constructors, you can use a builder:
|
||
fFullThreadStackDump = enable; | ||
return this; | ||
} | ||
|
||
public Statement apply(Statement base, Description description) { | ||
return new FailOnTimeout(base, fTimeout, fTimeUnit, fLookForStuckThread); | ||
return new FailOnTimeout(base, fTimeout, fTimeUnit, fLookForStuckThread, fFullThreadStackDump); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO this is far too many parameters. Could we create a Builder for FailOnTimeout and get rid of all of these constructors? See Effective Java, Second Edition, Item 2.
I personally like the style where there is a private constructor that takes in an instance of the Builder.