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;
   }
 }