rzo1 opened a new issue, #1506:
URL: https://github.com/apache/opennlp/issues/1506

   `AbstractClassPathModelFinder.findModels(boolean)` is not thread-safe. Since 
`DefaultClassPathModelProvider` delegates every `load(..)` call to it, loading 
models via a shared `ClassPathModelProvider` from several threads can fail.
   
   Affected: 3.0.0-M6 and current `main` 
(`opennlp-core/opennlp-model-resolver`).
   
   ```java
   if (this.models == null || reloadCache) {
     final List<URI> classpathModels = getMatchingURIs("*.bin", getContext());
     final List<URI> classPathProperties = getMatchingURIs("model.properties", 
getContext());
   
     this.models = new HashSet<>();      // published before it is filled
     for (URI model : classpathModels) {
       ...
       this.models.add(new ClassPathModelEntry(model, Optional.ofNullable(m)));
     }
   }
   return this.models;
   ```
   
   Problems:
   
   1. The (non-volatile) field is assigned an empty `HashSet` before it is 
populated. A second thread calling `findModels(false)` meanwhile sees `models 
!= null` and gets an empty or partially filled set, so 
`ClassPathModelLoader.load(..)` does not find a model that is actually on the 
classpath.
   2. The returned set is the live, mutable cache. A concurrent first call or 
`reloadCache = true` adds to a `HashSet` while another thread iterates over it 
in the loader, which can throw `ConcurrentModificationException` or miss 
entries.
   3. Two threads that both see `models == null` scan the classpath twice and 
overwrite each other's set.
   
   We hit this in an application server where a shared provider loads different 
models (sentence detector, tokenizer, POS tagger) lazily from concurrent 
requests: the first parallel loads randomly failed or found no model. As a 
workaround we serialize all calls to the provider with an external lock.
   
   Suggested fix: build the set in a local variable and publish it only once it 
is complete, as an unmodifiable set via a `volatile` field (or an 
`AtomicReference`). Guard the scan with a lock, or use double-checked locking, 
so the classpath is scanned only once for concurrent first calls. Also document 
the thread-safety of `ClassPathModelFinder` and `ClassPathModelProvider` in 
their Javadoc. A test could start N threads behind a `CountDownLatch` and call 
`findModels(false)` on a fresh finder, asserting that every caller gets the 
complete set.
   


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