diff --git a/core/impl-base/src/main/java/org/jboss/arquillian/core/impl/ManagerImpl.java b/core/impl-base/src/main/java/org/jboss/arquillian/core/impl/ManagerImpl.java index 19478b987..ac8890725 100644 --- a/core/impl-base/src/main/java/org/jboss/arquillian/core/impl/ManagerImpl.java +++ b/core/impl-base/src/main/java/org/jboss/arquillian/core/impl/ManagerImpl.java @@ -39,6 +39,7 @@ import org.jboss.arquillian.core.spi.Manager; import org.jboss.arquillian.core.spi.NonManagedObserver; import org.jboss.arquillian.core.spi.ObserverMethod; +import org.jboss.arquillian.core.spi.ThreadLocalUtil; import org.jboss.arquillian.core.spi.Validate; import org.jboss.arquillian.core.spi.context.ApplicationContext; import org.jboss.arquillian.core.spi.context.Context; @@ -279,6 +280,9 @@ public void shutdown() { runtimeLogger.clear(); handledThrowables.remove(); + + //Force cleanup ThreadLocal: + ThreadLocalUtil.forceCleanupThreadLocal(handledThrowables); } if (shutdownException != null) { UncheckedThrow.throwUnchecked(shutdownException); diff --git a/core/spi/src/main/java/org/jboss/arquillian/core/spi/ThreadLocalUtil.java b/core/spi/src/main/java/org/jboss/arquillian/core/spi/ThreadLocalUtil.java new file mode 100644 index 000000000..60e059f5f --- /dev/null +++ b/core/spi/src/main/java/org/jboss/arquillian/core/spi/ThreadLocalUtil.java @@ -0,0 +1,65 @@ +package org.jboss.arquillian.core.spi; + +import java.lang.reflect.Method; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class ThreadLocalUtil { + private static Logger log = Logger.getLogger(ThreadLocalUtil.class.getName()); + + /** + * Force cleanup of a thread local variable: using reflection, all entries for the "ThreadLocalMap" are removed. + * The entries are searched for all active threads. + * + * @param threadLocal This is the threadlocal variable to be cleaned up. + */ + public static void forceCleanupThreadLocal(ThreadLocal threadLocal) { + //Get all threads: + ThreadGroup rootGroup = Thread.currentThread().getThreadGroup(); + ThreadGroup parentGroup; + while ((parentGroup = rootGroup.getParent()) != null) { + rootGroup = parentGroup; + } + Thread[] threads = new Thread[rootGroup.activeCount()]; + while (rootGroup.enumerate(threads, true ) == threads.length) { + threads = new Thread[threads.length * 2]; + } + //Cleanup for each thread: + for (Thread thread : threads) + { + //The "threads" array has NULL entries... + if (thread != null) + { + cleanThreadLocals(thread, threadLocal); + } + } + } + + /** + * Performs cleanup of the ThreadLocal entry for a specific thread. + * + * @param thread + * @param threadLocal + */ + private static void cleanThreadLocals(Thread thread, ThreadLocal threadLocal) { + try { + Class threadLocalClass = Class.forName("java.lang.ThreadLocal"); + Method getMapMethod = threadLocalClass.getDeclaredMethod("getMap", Thread.class); + getMapMethod.setAccessible(true); + + Object threadLocalMap = getMapMethod.invoke(threadLocal, thread); + + if (threadLocalMap != null) + { + Class threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); + Method removeMethod = threadLocalMapClass.getDeclaredMethod("remove", ThreadLocal.class); + removeMethod.setAccessible(true); + + removeMethod.invoke(threadLocalMap, threadLocal); + } + } catch(Exception e) { + // We will tolerate an exception here and just log it + log.log(Level.SEVERE, "Arquillian failed to cleanup threadlocals - did the Java API change?", e); + } + } +} diff --git a/core/spi/src/main/java/org/jboss/arquillian/core/spi/context/AbstractContext.java b/core/spi/src/main/java/org/jboss/arquillian/core/spi/context/AbstractContext.java index dbc566139..8cbdb7704 100644 --- a/core/spi/src/main/java/org/jboss/arquillian/core/spi/context/AbstractContext.java +++ b/core/spi/src/main/java/org/jboss/arquillian/core/spi/context/AbstractContext.java @@ -20,6 +20,8 @@ import java.util.Stack; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; + +import org.jboss.arquillian.core.spi.ThreadLocalUtil; import org.jboss.arquillian.core.spi.Validate; /** @@ -105,10 +107,15 @@ public void clearAll() { deactivateAll(); } activeStore.remove(); + + //Force cleanup ThreadLocal: + ThreadLocalUtil.forceCleanupThreadLocal(activeStore); + for (Map.Entry entry : stores.entrySet()) { entry.getValue().clear(); } stores.clear(); + } }