kec opened a new issue, #12731:
URL: https://github.com/apache/maven/issues/12731

   **Component:** Maven Core (Maven 4), `maven-impl` — 
`org.apache.maven.impl.model.reflection.ReflectionValueExtractor`
   
   **Affects:** 4.0.0-rc-5 (observed live); code inspection shows the same 
structure at tag `maven-4.0.0-rc-6`.
   
   ## Summary
   
   `ReflectionValueExtractor` keeps a static class-introspection cache:
   
   ```java
   private static final Map<Class<?>, WeakReference<ClassMap>> CLASS_MAPS = new 
WeakHashMap<>();
   ```
   
   `getClassMap` reads and writes it with an unsynchronized check-then-act. 
Under the multithreaded builder (`-T`), every plugin-parameter expression 
evaluation (`PluginParameterExpressionEvaluatorV4` → 
`ReflectionValueExtractor.evaluate`) can `put` into this map from a different 
builder thread concurrently.
   
   `WeakHashMap` re-links bucket chains entry-by-entry during 
`resize`/`transfer` (the pre-Java-8 `HashMap` design; `HashMap` was redesigned 
in Java 8 but `WeakHashMap` retains the old linkage). Two threads resizing 
simultaneously can interleave the re-links so a bucket chain becomes circular. 
No exception is thrown; the next operation that walks the bucket follows the 
cycle forever. The build hangs at 100% CPU with no output and no error — 
indefinitely.
   
   ## Observed incident
   
   A 4-project reactor (macOS/aarch64, JDK 25, `-T1C` via `.mvn/maven.config`, 
14 cores) running `mvn -B compile javadoc:jar -DskipTests` hung for 45+ 
minutes, one core pinned, zero filesystem writes. Thread dump excerpt:
   
   ```
   "mvn-builder-integration-tests-example" … cpu=2590789.93ms RUNNABLE
       at java.util.WeakHashMap.transfer([email protected]/WeakHashMap.java:540)
       at java.util.WeakHashMap.resize([email protected]/WeakHashMap.java:506)
       at java.util.WeakHashMap.put([email protected]/WeakHashMap.java:479)
       at 
org.apache.maven.impl.model.reflection.ReflectionValueExtractor.getClassMap(ReflectionValueExtractor.java:295)
       at 
org.apache.maven.impl.model.reflection.ReflectionValueExtractor.getPropertyValue(ReflectionValueExtractor.java:271)
       at 
org.apache.maven.impl.model.reflection.ReflectionValueExtractor.evaluate(ReflectionValueExtractor.java:208)
       at 
org.apache.maven.impl.model.reflection.ReflectionValueExtractor.evaluate(ReflectionValueExtractor.java:140)
       at 
org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4.evaluate(PluginParameterExpressionEvaluatorV4.java:156)
       at 
org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4.evaluate(PluginParameterExpressionEvaluatorV4.java:95)
       at 
org.apache.maven.plugin.PluginParameterExpressionEvaluatorV4.evaluate(PluginParameterExpressionEvaluatorV4.java:118)
       at 
org.codehaus.plexus.component.configurator.converters.basic.AbstractBasicConverter.fromExpression(AbstractBasicConverter.java:105)
       …
   ```
   
   43+ CPU-minutes inside `WeakHashMap.transfer` — the bucket-cycle signature. 
The main thread was parked in 
`MultiThreadedBuilder.multiThreadedProjectTaskSegmentBuild`, so the whole build 
waits forever on the corrupted worker.
   
   ## Reproduction character
   
   Intermittent by nature (the corrupting interleave needs two threads to 
resize the same table concurrently). Likelihood scales with 
parameter-expression density × builder threads: our reactor runs many plugin 
executions per module with `-T1C` on 14 cores and has hit this twice — once as 
this hang and once, weeks earlier, as a `ConcurrentModificationException` from 
the same call path (the "lucky" symptom of the same unsafe sharing). A tight 
loop constructing mojo configurations with many distinct bean classes across 
threads should reproduce it synthetically.
   
   ## Suggested fix
   
   Make the cache a `ConcurrentHashMap` keyed by `Class` with 
`WeakReference<ClassMap>` values (as now), or wrap access in synchronization. 
The map need not be weak-keyed for correctness of this fix so long as values 
remain weak; if weak keys are required for classloader hygiene, 
`Collections.synchronizedMap(new WeakHashMap<>())` is the minimal change, and a 
striped/concurrent weak cache the better one. Related precedent in the Maven 
family: maven-resolver's `GenericVersionScheme`/`WeakInternPool` 
thread-contention fix (and its follow-up after the synchronization showed 
hot-path cost) — the same hazard class, already accepted and fixed there.
   
   ## Impact
   
   Any Maven 4 `-T` build can hang forever with no diagnostic. In unattended CI 
a wedged release/build holds agents indefinitely — the failure mode is silence, 
not an error.
   
   Full thread dump available on request.


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