You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by gn...@apache.org on 2026/06/19 09:00:57 UTC
(maven) branch maven-4.0.x updated: [#12301] Use stack-passed Set instead of shared ConcurrentHashMap for activeModelReads (#12325)
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 <gn...@gmail.com>
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 <no...@anthropic.com>
---
.../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);
}
}
}