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]