This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch fix/12301-backport-threadlocal-4.0.x in repository https://gitbox.apache.org/repos/asf/maven.git
commit e00cda8d393b7ad66b714a4e84a80da282892772 Author: Guillaume Nodet <[email protected]> AuthorDate: Fri Jun 19 08:59:08 2026 +0200 [#12301] Use stack-passed Set instead of shared ConcurrentHashMap for activeModelReads 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); } } }
