diff --git a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
index 19bc194..c282bd2 100644
--- a/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
+++ b/util/src/test/java/org/killbill/billing/GuicyKillbillTestSuiteWithEmbeddedDB.java
@@ -18,6 +18,14 @@
package org.killbill.billing;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.lang.management.LockInfo;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MonitorInfo;
+import java.lang.management.ThreadInfo;
+import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
@@ -97,6 +105,7 @@ public class GuicyKillbillTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
@AfterSuite(groups = "slow")
public void afterSuite() throws Exception {
if (hasFailed()) {
+ threadDump();
dumpDB();
log.error("**********************************************************************************************");
log.error("*** TESTS HAVE FAILED - LEAVING DB RUNNING FOR DEBUGGING - MAKE SURE TO KILL IT ONCE DONE ****");
@@ -167,4 +176,77 @@ public class GuicyKillbillTestSuiteWithEmbeddedDB extends GuicyKillbillTestSuite
log.error("Unable to dump DB");
}
}
+
+ private void threadDump() {
+ final ThreadInfo[] threads = ManagementFactory.getThreadMXBean().dumpAllThreads(true, true);
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ final PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, Charset.forName("UTF-8")));
+
+ for (int ti = threads.length - 1; ti >= 0; ti--) {
+ final ThreadInfo t = threads[ti];
+ writer.printf("\"%s\" id=%d state=%s",
+ t.getThreadName(),
+ t.getThreadId(),
+ t.getThreadState());
+ final LockInfo lock = t.getLockInfo();
+ if (lock != null && t.getThreadState() != Thread.State.BLOCKED) {
+ writer.printf("%n - waiting on <0x%08x> (a %s)",
+ lock.getIdentityHashCode(),
+ lock.getClassName());
+ writer.printf("%n - locked <0x%08x> (a %s)",
+ lock.getIdentityHashCode(),
+ lock.getClassName());
+ } else if (lock != null && t.getThreadState() == Thread.State.BLOCKED) {
+ writer.printf("%n - waiting to lock <0x%08x> (a %s)",
+ lock.getIdentityHashCode(),
+ lock.getClassName());
+ }
+
+ if (t.isSuspended()) {
+ writer.print(" (suspended)");
+ }
+
+ if (t.isInNative()) {
+ writer.print(" (running in native)");
+ }
+
+ writer.println();
+ if (t.getLockOwnerName() != null) {
+ writer.printf(" owned by %s id=%d%n", t.getLockOwnerName(), t.getLockOwnerId());
+ }
+
+ final StackTraceElement[] elements = t.getStackTrace();
+ final MonitorInfo[] monitors = t.getLockedMonitors();
+
+ for (int i = 0; i < elements.length; i++) {
+ final StackTraceElement element = elements[i];
+ writer.printf(" at %s%n", element);
+ for (int j = 1; j < monitors.length; j++) {
+ final MonitorInfo monitor = monitors[j];
+ if (monitor.getLockedStackDepth() == i) {
+ writer.printf(" - locked %s%n", monitor);
+ }
+ }
+ }
+ writer.println();
+
+ final LockInfo[] locks = t.getLockedSynchronizers();
+ if (locks.length > 0) {
+ writer.printf(" Locked synchronizers: count = %d%n", locks.length);
+ for (LockInfo l : locks) {
+ writer.printf(" - %s%n", l);
+ }
+ writer.println();
+ }
+ }
+
+ writer.println();
+ writer.flush();
+
+ log.error("********************************************");
+ log.error("*** TESTS HAVE FAILED - DUMPING THREADS ****");
+ log.error("********************************************\n");
+ log.error(out.toString());
+ log.error("********************************************");
+ }
}