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..cd1525e11 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 @@ -31,6 +31,7 @@ import org.jboss.arquillian.core.api.threading.ExecutorService; import org.jboss.arquillian.core.impl.context.ApplicationContextImpl; import org.jboss.arquillian.core.impl.threading.ThreadedExecutorService; +import org.jboss.arquillian.core.spi.ArquillianThreadLocal; import org.jboss.arquillian.core.spi.EventContext; import org.jboss.arquillian.core.spi.EventPoint; import org.jboss.arquillian.core.spi.Extension; @@ -66,8 +67,8 @@ public class ManagerImpl implements Manager { * the higher level event will get the exception and re-fire it on the bus. We need to keep track of which exceptions * has been handled in the call chain so we can re-throw without re-firing on a higher level. */ - private ThreadLocal>> handledThrowables = - new ThreadLocal>>() { + private ArquillianThreadLocal>> handledThrowables = + new ArquillianThreadLocal>>() { @Override protected Set> initialValue() { return new HashSet>(); @@ -279,6 +280,8 @@ public void shutdown() { runtimeLogger.clear(); handledThrowables.remove(); + //Force cleanup: + handledThrowables.clear(); } if (shutdownException != null) { UncheckedThrow.throwUnchecked(shutdownException); diff --git a/core/spi/src/main/java/org/jboss/arquillian/core/spi/ArquillianThreadLocal.java b/core/spi/src/main/java/org/jboss/arquillian/core/spi/ArquillianThreadLocal.java new file mode 100644 index 000000000..b27faaeda --- /dev/null +++ b/core/spi/src/main/java/org/jboss/arquillian/core/spi/ArquillianThreadLocal.java @@ -0,0 +1,59 @@ +package org.jboss.arquillian.core.spi; + +import java.util.Hashtable; + +/** + * Mapping for ThreadId to a value. Same as "ThreadLocal", but with simpler cleanup. + * + */ +public class ArquillianThreadLocal { + private Hashtable table = new Hashtable(); + + protected T initialValue() { + return null; + } + + /** + * Returns the value in the current thread's copy of this + * thread-local variable. If the variable has no value for the + * current thread, it is first initialized to the value returned + * by an invocation of the {@link #initialValue} method. + * + * @return the current thread's value of this thread-local + */ + public T get() { + Thread t = Thread.currentThread(); + long threadId = t.getId(); + + if (table.containsKey(threadId)) { + return table.get(threadId); + } + else { + T value = initialValue(); + table.put(threadId, value); + return value; + } + } + + /** + * Removes the current thread's value for this thread-local + * variable. + * + */ + public void remove() { + Thread t = Thread.currentThread(); + long threadId = t.getId(); + + if (table.containsKey(threadId)) { + table.remove(threadId); + } + } + + /** + * Clears the cache + */ + public void clear() { + table.clear(); + } +} + 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..b18fa8eb9 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.ArquillianThreadLocal; import org.jboss.arquillian.core.spi.Validate; /** @@ -33,7 +35,7 @@ public abstract class AbstractContext implements Context, IdBoundContext { private ConcurrentHashMap stores; - private ThreadLocal>> activeStore = new ThreadLocal>>() { + private ArquillianThreadLocal>> activeStore = new ArquillianThreadLocal>>() { @Override protected Stack> initialValue() { @@ -105,6 +107,8 @@ public void clearAll() { deactivateAll(); } activeStore.remove(); + //Force cleanup: + activeStore.clear(); for (Map.Entry entry : stores.entrySet()) { entry.getValue().clear(); }