You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@uima.apache.org by re...@apache.org on 2021/12/13 11:33:28 UTC
[uima-uimaj] 01/01: [UIMA-6400] UimaContextHolder threadlocal can leak
This is an automated email from the ASF dual-hosted git repository.
rec pushed a commit to branch bugfix/UIMA-6400-UimaContextHolder-threadlocal-can-leak
in repository https://gitbox.apache.org/repos/asf/uima-uimaj.git
commit 8ede6d4ddbfc0189dabc4d7a3083338fc3b4ba5b
Author: Richard Eckart de Castilho <re...@apache.org>
AuthorDate: Mon Dec 13 12:33:14 2021 +0100
[UIMA-6400] UimaContextHolder threadlocal can leak
- Use a weak reference for the context in the context holder by default so that threads that are inheriting the context holder don't prevent the context from being garbage collected
---
.../src/docbook/ref.xml.component_descriptor.xml | 10 ++
.../java/org/apache/uima/UimaContextHolder.java | 123 ++++++++++++++++++---
2 files changed, 116 insertions(+), 17 deletions(-)
diff --git a/uima-docbook-references/src/docbook/ref.xml.component_descriptor.xml b/uima-docbook-references/src/docbook/ref.xml.component_descriptor.xml
index ec084c6..5982b13 100644
--- a/uima-docbook-references/src/docbook/ref.xml.component_descriptor.xml
+++ b/uima-docbook-references/src/docbook/ref.xml.component_descriptor.xml
@@ -2090,6 +2090,16 @@ if (uimaContext != null) {
will be null if <literal>getUimaContext</literal> is not invoked by an annotator or flow
controller on the same thread or a child thread.
</para>
+ <para>
+ Since UIMA 3.2.1, the context is stored in the InheritableThreadLocal as a weak reference.
+ This ensures that any long-running threads spawned while the context is set do not
+ prevent garbage-collection of the context when the context is destroyed. If a child
+ thread should really retain a strong reference to the context, it should obtain the
+ context and store it in a field or in another ThreadLocal variable. For backwards
+ compatibility, the old behavior of using a strong reference by default can be enabled
+ by setting the system property <literal>uima.context_holder_reference_type</literal>
+ to <literal>STRONG</literal>.
+ </para>
</section>
<section id="&tp;aes.other_uses_for_external_configuration_parameters">
diff --git a/uimaj-core/src/main/java/org/apache/uima/UimaContextHolder.java b/uimaj-core/src/main/java/org/apache/uima/UimaContextHolder.java
index 9520b87..47fa839 100644
--- a/uimaj-core/src/main/java/org/apache/uima/UimaContextHolder.java
+++ b/uimaj-core/src/main/java/org/apache/uima/UimaContextHolder.java
@@ -16,50 +16,139 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package org.apache.uima;
+import static java.util.Collections.synchronizedMap;
+
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.apache.uima.internal.util.Misc;
+
/**
- * This class holds the UimaContext for the current thread, or a parent thread.
- * The getContext method may be used by any plain Java class invoked by an annotator,
- * The POJO must run in the same thread or a child thread of the annotator.
+ * This class holds the UimaContext for the current thread, or a parent thread. The getContext
+ * method may be used by any plain Java class invoked by an annotator, The POJO must run in the same
+ * thread or a child thread of the annotator.
*
- * For example a POJO can access the shared External Override Settings with:
- * String paramValue = UimaContextHolder.getContext().getSetting(paramName);
+ * For example a POJO can access the shared External Override Settings with: String paramValue =
+ * UimaContextHolder.getContext().getSetting(paramName);
*/
public class UimaContextHolder {
-
- private static InheritableThreadLocal<UimaContext> threadLocalContext = new InheritableThreadLocal<>();
-
+
+ private static final String TRACK_CONTEXT_HOLDER_TRACKING = "uima.enable_context_holder_tracking";
+ private static final String CONTEXT_HOLDER_REFERENCE_TYPE = "uima.context_holder_reference_type";
+
+ private static final boolean IS_TRACK_CONTEXT_HOLDER_TRACKING = Misc
+ .getNoValueSystemProperty(TRACK_CONTEXT_HOLDER_TRACKING);
+
+ private static final ContextHolderReferenceType CONTEXT_HOLDER_REFERENCE_TYPE_VALUE = ContextHolderReferenceType
+ .valueOf(System.getProperty(CONTEXT_HOLDER_REFERENCE_TYPE,
+ ContextHolderReferenceType.WEAK.toString()));
+
+ private static final InheritableThreadLocal<Object> THREAD_LOCAL_CONTEXT = new InheritableThreadLocal<>();
+
+ private static final Map<UimaContext, StackTraceElement[]> CONTEXT_SETTERS = IS_TRACK_CONTEXT_HOLDER_TRACKING
+ ? synchronizedMap(new WeakHashMap<>())
+ : null;
+
+ private UimaContextHolder() {
+ // No instances
+ }
+
/**
* Get the UimaContext for this thread
*
- * @return the thread-specific UimaContext
+ * @return the thread-specific UimaContext
*/
public static UimaContext getContext() {
- return threadLocalContext.get();
+ Object obj = THREAD_LOCAL_CONTEXT.get();
+
+ if (!(obj instanceof Reference)) {
+ return (UimaContext) obj;
+ }
+
+ @SuppressWarnings("unchecked")
+ Reference<UimaContext> ref = (Reference<UimaContext>) obj;
+ UimaContext context = ref.get();
+ if (context == null) {
+ THREAD_LOCAL_CONTEXT.set(null);
+ }
+
+ return context;
}
-
+
/**
* Sets the UimaContext for the current thread.
* <p>
* NOTE - Should be used only by the UIMA Framework.
*
- * @param uimaContext - new UimaContext for this thread
+ * @param uimaContext
+ * - new UimaContext for this thread
* @return - previous UimaContext for this thread
*/
public static UimaContext setContext(UimaContext uimaContext) {
- UimaContext prevContext = threadLocalContext.get();
- threadLocalContext.set(uimaContext);
+ Object prevContextObj = THREAD_LOCAL_CONTEXT.get();
+ @SuppressWarnings("unchecked")
+ UimaContext prevContext = prevContextObj instanceof Reference
+ ? ((Reference<UimaContext>) prevContextObj).get()
+ : (UimaContext) prevContextObj;
+
+ if (uimaContext == null) {
+ // Clear context
+ THREAD_LOCAL_CONTEXT.set(null);
+ if (prevContext != null && CONTEXT_SETTERS != null) {
+ CONTEXT_SETTERS.remove(prevContext);
+ }
+ } else {
+ // Set context with the configured reference type
+ THREAD_LOCAL_CONTEXT.set(makeRef(uimaContext));
+ if (CONTEXT_SETTERS != null) {
+ CONTEXT_SETTERS.put(uimaContext, new Exception().getStackTrace());
+ }
+ }
+
return prevContext;
}
-
+
+ private static Object makeRef(UimaContext aContext) {
+ switch (CONTEXT_HOLDER_REFERENCE_TYPE_VALUE) {
+ case SOFT:
+ return new SoftReference<>(aContext);
+ case WEAK:
+ return new WeakReference<>(aContext);
+ case STRONG:
+ return aContext;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported reference type: [" + CONTEXT_HOLDER_REFERENCE_TYPE_VALUE + "]");
+ }
+ }
+
/**
* Clears the UimaContext entry for the current thread
* <p>
* NOTE - Should be used only by the UIMA Framework.
*/
public static void clearContext() {
- threadLocalContext.set(null);
+ if (CONTEXT_SETTERS != null) {
+ Object prevContextObj = THREAD_LOCAL_CONTEXT.get();
+ @SuppressWarnings("unchecked")
+ UimaContext prevContext = prevContextObj instanceof Reference
+ ? ((Reference<UimaContext>) prevContextObj).get()
+ : (UimaContext) prevContextObj;
+
+ if (prevContext != null) {
+ CONTEXT_SETTERS.remove(prevContext);
+ }
+ }
+
+ THREAD_LOCAL_CONTEXT.set(null);
+ }
+
+ private enum ContextHolderReferenceType {
+ STRONG, WEAK, SOFT;
}
}