Skip to content

Commit

Permalink
ISPN-10106 Ignore JCache tck-runner thread leaks
Browse files Browse the repository at this point in the history
  • Loading branch information
danberindei committed May 3, 2019
1 parent 2f8944b commit 50b8398
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public class ThreadLeakChecker {
").*");
private static final String ARQUILLIAN_CONSOLE_CONSUMER =
"org.jboss.as.arquillian.container.managed.ManagedDeployableContainer$ConsoleConsumer";
private static final boolean ENABLED =
"true".equalsIgnoreCase(System.getProperty("infinispan.test.checkThreadLeaks", "true"));

private static Logger log = Logger.getLogger(ThreadLeakChecker.class);
private static volatile long lastUpdate = 0;
Expand Down Expand Up @@ -137,7 +139,7 @@ public static void testFinished(String testName) {
}
}

public static void updateThreadOwnership(List<String> availableOwners) {
private static void updateThreadOwnership(List<String> availableOwners) {
// Update the thread ownership information
Set<Thread> currentThreads = getThreadsSnapshot();
runningThreads.keySet().retainAll(currentThreads);
Expand All @@ -152,6 +154,9 @@ public static void updateThreadOwnership(List<String> availableOwners) {
* Assumes that no tests are running.
*/
public static void checkForLeaks() {
if (!ENABLED)
return;

lock.lock();
try {
assert runningTests.isEmpty() : "Tests are still running: " + runningTests;
Expand Down Expand Up @@ -181,7 +186,7 @@ private static void performCheck() {
if (!leaks.isEmpty()) {
for (LeakInfo leakInfo : leaks) {
log.warnf("Possible leaked thread:\n%s", prettyPrintStacktrace(leakInfo.thread));
leakInfo.reported = true;
leakInfo.markReported();
}
// Strategies for debugging test suite thread leaks
// Use -Dinfinispan.test.parallel.threads=3 (or even less) to narrow down source tests
Expand All @@ -197,28 +202,26 @@ private static void performCheck() {
private static List<LeakInfo> computeLeaks() {
List<LeakInfo> leaks = new ArrayList<>();
for (LeakInfo leakInfo : runningThreads.values()) {
if (!leakInfo.reported && leakInfo.thread.isAlive() && !ignore(leakInfo)) {
if (leakInfo.shouldReport() && leakInfo.thread.isAlive() && !ignore(leakInfo.thread)) {
leaks.add(leakInfo);
}
}
return leaks;
}

private static boolean ignore(LeakInfo leakInfo) {
private static boolean ignore(Thread thread) {
// System threads (running before the first test) have no potential owners
String threadName = leakInfo.thread.getName();
if (leakInfo.potentialOwnerTests.isEmpty())
return true;
String threadName = thread.getName();
if (IGNORED_THREADS_REGEX.matcher(threadName).matches())
return true;

if (leakInfo.thread.getName().startsWith("Thread-")) {
if (thread.getName().startsWith("Thread-")) {
// Special check for ByteMan, because nobody calls TransformListener.terminate()
if (leakInfo.thread.getClass().getName().equals("org.jboss.byteman.agent.TransformListener"))
if (thread.getClass().getName().equals("org.jboss.byteman.agent.TransformListener"))
return true;

// Special check for Arquillian, because it uses an unnamed thread to read from the container console
StackTraceElement[] s = leakInfo.thread.getStackTrace();
StackTraceElement[] s = thread.getStackTrace();
for (StackTraceElement ste : s) {
if (ste.getClassName().equals(ARQUILLIAN_CONSOLE_CONSUMER)) {
return true;
Expand All @@ -228,7 +231,7 @@ private static boolean ignore(LeakInfo leakInfo) {
return false;
}

public static String prettyPrintStacktrace(Thread thread) {
private static String prettyPrintStacktrace(Thread thread) {
// "management I/O-2" #55 prio=5 os_prio=0 tid=0x00007fe6a8134000 nid=0x7f9d runnable
// [0x00007fe64e4db000]
// java.lang.Thread.State:RUNNABLE
Expand All @@ -244,12 +247,6 @@ public static String prettyPrintStacktrace(Thread thread) {
return sb.toString();
}

private static <T> List<T> drain(BlockingQueue<T> blockingQueue) {
List<T> list = new ArrayList<>();
blockingQueue.drainTo(list);
return list;
}

private static Set<Thread> getThreadsSnapshot() {
ThreadGroup group = Thread.currentThread().getThreadGroup();
while (group.getParent() != null) {
Expand All @@ -267,6 +264,9 @@ private static Set<Thread> getThreadsSnapshot() {
}
}

/**
* Ignore threads matching a predicate.
*/
public static void ignoreThreadsMatching(Predicate<Thread> filter) {
// Update the thread ownership information
Set<Thread> currentThreads = getThreadsSnapshot();
Expand All @@ -277,36 +277,52 @@ public static void ignoreThreadsMatching(Predicate<Thread> filter) {
}
}

/**
* Ignore a running thread.
*/
public static void ignoreThread(Thread thread) {
LeakInfo leakInfo = runningThreads.computeIfAbsent(thread, k -> new LeakInfo(thread, Collections.emptyList()));
leakInfo.ignore();
}

public static void ignoreThreadsContaining(String threadNameSubstring) {
ignoreThreadsMatching(thread -> thread.getName().matches(".*" + threadNameSubstring + ".*"));
/**
* Ignore threads containing a regex.
*/
public static void ignoreThreadsContaining(String threadNameRegex) {
Pattern pattern = Pattern.compile(".*" + threadNameRegex + ".*");
ignoreThreadsMatching(thread -> pattern.matcher(thread.getName()).matches());
}

private static class LeakInfo {
final Thread thread;
final List<String> potentialOwnerTests;
boolean reported;
boolean ignored;

LeakInfo(Thread thread, List<String> potentialOwnerTests) {
this.thread = thread;
this.potentialOwnerTests = potentialOwnerTests;
}

public void ignore() {
void ignore() {
ignored = true;
}

void markReported() {
reported = true;
}

public boolean shouldReport() {
return !reported;
boolean shouldReport() {
return !ignored && !reported;
}

@Override
public String toString() {
return "{" + thread.getName() + ": possible sources " + potentialOwnerTests + "}";
if (ignored) {
return "{" + thread.getName() + ": ignored}";
}
String reportedString = reported ? " reported, " : "";
return "{" + thread.getName() + ": " + reportedString + "possible sources " + potentialOwnerTests + "}";
}
}
}
1 change: 1 addition & 0 deletions jcache/tck-runner/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@
<log4j.configurationFile>${log4j.configurationFile}</log4j.configurationFile>
<java.net.preferIPv4Stack>true</java.net.preferIPv4Stack>
<jgroups.join_timeout>2000</jgroups.join_timeout>
<infinispan.test.checkThreadLeaks>false</infinispan.test.checkThreadLeaks>
</systemPropertyVariables>
<argLine>${forkJvmArgs} ${testjvm.jigsawArgs}</argLine>
<properties>
Expand Down

0 comments on commit 50b8398

Please sign in to comment.