This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch maven-4.0.x
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/maven-4.0.x by this push:
new 93f76f8b31 [#12301] Use stack-passed Set instead of shared
ConcurrentHashMap for activeModelReads (#12325)
93f76f8b31 is described below
commit 93f76f8b3142a12a3dfb3cb7e9d574d8ec6a3d78
Author: Guillaume Nodet <[email protected]>
AuthorDate: Fri Jun 19 11:00:51 2026 +0200
[#12301] Use stack-passed Set instead of shared ConcurrentHashMap for
activeModelReads (#12325)
Replace the shared ConcurrentHashMap.newKeySet() with a Set<Path> passed
through the call stack: doReadFileModel() → getEnhancedProperties() →
readFileModel(). Each call chain carries its own set, preventing recursive
cycles within the same call stack without interfering with parallel threads
in PhasingExecutor.
This avoids the race condition where a path added by one thread blocks a
parallel thread's getEnhancedProperties() from reading that model, while
still preventing the StackOverflowError from GH-12301.
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.../maven/impl/model/DefaultModelBuilder.java | 36 ++++++++++------------
1 file changed, 16 insertions(+), 20 deletions(-)
diff --git
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
index 4898b82be7..a877ba7cc9 100644
---
a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
+++
b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelBuilder.java
@@ -276,11 +276,6 @@ List<RemoteRepository> getExternalRepositories() {
// Contains both GAV coordinates (groupId:artifactId:version) and file
paths
final Set<String> parentChain;
- // Tracks file paths currently being read by doReadFileModel, shared
across
- // all derived sessions to prevent recursive loops between parent
resolution
- // and enhanced property resolution (GH-12301)
- final Set<Path> activeModelReads;
-
ModelBuilderSessionState(ModelBuilderRequest request) {
this(
request.getSession(),
@@ -291,8 +286,7 @@ List<RemoteRepository> getExternalRepositories() {
List.of(),
repos(request),
repos(request),
- new LinkedHashSet<>(),
- ConcurrentHashMap.newKeySet());
+ new LinkedHashSet<>());
}
static List<RemoteRepository> repos(ModelBuilderRequest request) {
@@ -312,8 +306,7 @@ private ModelBuilderSessionState(
List<RemoteRepository> pomRepositories,
List<RemoteRepository> externalRepositories,
List<RemoteRepository> repositories,
- Set<String> parentChain,
- Set<Path> activeModelReads) {
+ Set<String> parentChain) {
this.session = session;
this.request = request;
this.result = result;
@@ -323,7 +316,6 @@ private ModelBuilderSessionState(
this.externalRepositories = externalRepositories;
this.repositories = repositories;
this.parentChain = parentChain;
- this.activeModelReads = activeModelReads;
this.result.setSource(this.request.getSource());
}
@@ -374,8 +366,7 @@ ModelBuilderSessionState derive(ModelBuilderRequest
request, DefaultModelBuilder
pomRepositories,
derivedExtRepos,
derivedRepos,
- new LinkedHashSet<>(),
- activeModelReads);
+ new LinkedHashSet<>());
}
@Override
@@ -682,7 +673,7 @@ String replaceCiFriendlyVersion(Map<String, String>
properties, String version)
* are available for CI-friendly version processing and repository URL
interpolation.
* It also includes directory-related properties that may be needed
during profile activation.
*/
- private Map<String, String> getEnhancedProperties(Model model, Path
rootDirectory) {
+ private Map<String, String> getEnhancedProperties(Model model, Path
rootDirectory, Set<Path> activeModelReads) {
Map<String, String> properties = new HashMap<>();
// Add directory-specific properties first, as they may be needed
for profile activation
@@ -715,7 +706,7 @@ private Map<String, String> getEnhancedProperties(Model
model, Path rootDirector
if (isParentWithinRootDirectory(rootModelPath,
rootDirectory)
&&
!activeModelReads.contains(rootModelPath.normalize())) {
Model rootModel =
-
derive(Sources.buildSource(rootModelPath)).readFileModel();
+
derive(Sources.buildSource(rootModelPath)).readFileModel(activeModelReads);
properties.putAll(getPropertiesWithProfiles(rootModel,
properties));
}
}
@@ -1494,14 +1485,18 @@ private List<Profile> getActiveProfiles(
}
Model readFileModel() throws ModelBuilderException {
- Model model = cache(request.getSource(), FILE,
this::doReadFileModel);
+ return readFileModel(new HashSet<>());
+ }
+
+ Model readFileModel(Set<Path> activeModelReads) throws
ModelBuilderException {
+ Model model = cache(request.getSource(), FILE, () ->
doReadFileModel(activeModelReads));
// set the file model in the result outside the cache
result.setFileModel(model);
return model;
}
@SuppressWarnings("checkstyle:methodlength")
- Model doReadFileModel() throws ModelBuilderException {
+ Model doReadFileModel(Set<Path> activeModelReads) throws
ModelBuilderException {
ModelSource modelSource = request.getSource();
Model model;
Path rootDirectory;
@@ -1509,7 +1504,8 @@ Model doReadFileModel() throws ModelBuilderException {
setSource(modelSource.getLocation());
logger.debug("Reading file model from " +
modelSource.getLocation());
Path sourcePath = modelSource.getPath();
- boolean trackRead = sourcePath != null &&
activeModelReads.add(sourcePath.normalize());
+ Path normalizedPath = sourcePath != null ? sourcePath.normalize()
: null;
+ boolean trackRead = normalizedPath != null &&
activeModelReads.add(normalizedPath);
try {
try {
boolean strict = isBuildRequest();
@@ -1614,7 +1610,7 @@ Model doReadFileModel() throws ModelBuilderException {
}
Model parentModel =
-
derive(Sources.buildSource(pomPath)).readFileModel();
+
derive(Sources.buildSource(pomPath)).readFileModel(activeModelReads);
String parentGroupId = getGroupId(parentModel);
String parentArtifactId =
parentModel.getArtifactId();
String parentVersion = getVersion(parentModel);
@@ -1666,7 +1662,7 @@ Model doReadFileModel() throws ModelBuilderException {
// Enhanced property resolution with profile activation
for CI-friendly versions and repository URLs
// This includes directory properties, profile properties,
and user properties
- Map<String, String> properties =
getEnhancedProperties(model, rootDirectory);
+ Map<String, String> properties =
getEnhancedProperties(model, rootDirectory, activeModelReads);
// CI friendly version processing with profile-aware
properties
model = model.with()
@@ -1722,7 +1718,7 @@ Model doReadFileModel() throws ModelBuilderException {
return model;
} finally {
if (trackRead) {
- activeModelReads.remove(sourcePath.normalize());
+ activeModelReads.remove(normalizedPath);
}
}
}