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);
                 }
             }
         }

Reply via email to