krickert commented on code in PR #1003:
URL: https://github.com/apache/opennlp/pull/1003#discussion_r3059023627


##########
opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/featuregen/CachedFeatureGenerator.java:
##########
@@ -21,62 +21,90 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import opennlp.tools.commons.ThreadSafe;
 import opennlp.tools.util.Cache;
 
 /**
  * Caches features of the aggregated {@link AdaptiveFeatureGenerator 
generators}.
+ * <p>
+ * The cache is maintained per-thread via {@link ThreadLocal}, making this 
class safe for
+ * concurrent use from multiple threads. Each thread gets its own independent 
cache that is
+ * cleared when a new sentence (token array) is encountered.
+ * <p>
+ * <b>Note:</b> In container environments with classloader isolation (e.g. 
Jakarta EE),
+ * {@link ThreadLocal} state may pin the classloader. Ensure instances do not 
outlive
+ * the application's lifecycle, or call {@link ThreadLocal#remove()} on pooled 
threads.
  *
  * @see Cache
  */
+@ThreadSafe
 public class CachedFeatureGenerator implements AdaptiveFeatureGenerator {
 
+  /**
+   * System property to disable the feature cache globally.
+   * Set to {@code "true"} to bypass caching (useful for benchmarking).
+   */
+  public static final String DISABLE_CACHE_PROPERTY = 
"opennlp.featuregen.cache.disabled";
+
   private final AdaptiveFeatureGenerator generator;
+  private final boolean cacheEnabled;
 
-  private String[] prevTokens;
+  private final ThreadLocal<CacheState> threadState =
+      ThreadLocal.withInitial(() -> new CacheState(new Cache<>(100)));
 
-  private final Cache<Integer, List<String>> contextsCache;
+  private static final class CacheState {
+    private String[] prevTokens;
+    private final Cache<Integer, List<String>> cache;
 
-  private long numberOfCacheHits;
-  private long numberOfCacheMisses;
+    CacheState(Cache<Integer, List<String>> cache) {
+      this.cache = cache;
+    }
+  }
 
+  /**
+   * @deprecated Use {@link #CachedFeatureGenerator(AdaptiveFeatureGenerator)} 
instead.
+   */
   @Deprecated
   public CachedFeatureGenerator(AdaptiveFeatureGenerator... generators) {
     this.generator = new AggregatedFeatureGenerator(generators);
-    contextsCache = new Cache<>(100);
+    this.cacheEnabled = !Boolean.getBoolean(DISABLE_CACHE_PROPERTY);
   }
 
   public CachedFeatureGenerator(AdaptiveFeatureGenerator generator) {
     this.generator = generator;
-    contextsCache = new Cache<>(100);
+    this.cacheEnabled = !Boolean.getBoolean(DISABLE_CACHE_PROPERTY);
   }
 
   @Override
   public void createFeatures(List<String> features, String[] tokens, int index,
       String[] previousOutcomes) {
 
+    if (!cacheEnabled) {

Review Comment:
   Good call. The fast path (`!cacheEnabled`) is unchanged, and the cached path 
is now in a proper `else` so the structure reads clearly. Landed in 3a43f71.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to