diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java index 826ffe8fc4c5..bb8f524749da 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ModelBuilderRequest.java @@ -332,8 +332,8 @@ private static class DefaultModelBuilderRequest extends BaseRequest imp this.activeProfileIds = activeProfileIds != null ? List.copyOf(activeProfileIds) : List.of(); this.inactiveProfileIds = inactiveProfileIds != null ? List.copyOf(inactiveProfileIds) : List.of(); this.systemProperties = - systemProperties != null ? Map.copyOf(systemProperties) : session.getSystemProperties(); - this.userProperties = userProperties != null ? Map.copyOf(userProperties) : session.getUserProperties(); + Map.copyOf(systemProperties != null ? systemProperties : session.getSystemProperties()); + this.userProperties = Map.copyOf(userProperties != null ? userProperties : session.getUserProperties()); this.repositoryMerging = repositoryMerging; this.repositories = repositories != null ? List.copyOf(validate(repositories)) : null; this.lifecycleBindingsInjector = lifecycleBindingsInjector; diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/cache/AbstractRequestCache.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/cache/AbstractRequestCache.java index 0b7fac393bf8..4d655c762083 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/cache/AbstractRequestCache.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/cache/AbstractRequestCache.java @@ -19,7 +19,7 @@ package org.apache.maven.impl.cache; import java.util.ArrayList; -import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -60,7 +60,14 @@ public abstract class AbstractRequestCache implements RequestCache { @SuppressWarnings("all") public , REP extends Result> REP request(REQ req, Function supplier) { CachingSupplier cs = doCache(req, supplier); - return cs.apply(req); + try { + return cs.apply(req); + } catch (CachingSupplier.CyclicCacheAccessException e) { + // Re-entrant access from the same thread (e.g., a batch requests() computation + // triggered a singular request() that found the same cached entry). Compute + // directly with the caller's supplier to break the cycle. + return supplier.apply(req); + } } /** @@ -85,7 +92,7 @@ public , REP extends Result> REP request(REQ req, Fu @SuppressWarnings("unchecked") public , REP extends Result> List requests( List reqs, Function, List> supplier) { - final Map nonCachedResults = new HashMap<>(); + final Map nonCachedResults = new IdentityHashMap<>(); List> allResults = new ArrayList<>(reqs.size()); Function individualSupplier = req -> { diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/cache/CachingSupplier.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/cache/CachingSupplier.java index d1960b194484..85b2411e0169 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/cache/CachingSupplier.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/cache/CachingSupplier.java @@ -30,6 +30,8 @@ public class CachingSupplier implements Function { protected final Function supplier; protected volatile Object value; + // Guarded by synchronized(this) — tracks which thread is currently computing + private Thread computingThread; public CachingSupplier(Function supplier) { this.supplier = supplier; @@ -46,10 +48,18 @@ public REP apply(REQ req) { if ((v = value) == null) { synchronized (this) { if ((v = value) == null) { + if (computingThread == Thread.currentThread()) { + throw new CyclicCacheAccessException(); + } + computingThread = Thread.currentThread(); try { v = value = supplier.apply(req); + } catch (CyclicCacheAccessException e) { + throw e; } catch (Exception e) { v = value = new AltRes(e); + } finally { + computingThread = null; } } } @@ -60,6 +70,16 @@ public REP apply(REQ req) { return (REP) v; } + /** + * Thrown when a re-entrant call is detected on the same thread that is already + * computing this supplier's value. Prevents self-deadlock when a batch + * {@code requests()} computation triggers a singular {@code request()} that + * finds the same CachingSupplier in the cache. + */ + public static class CyclicCacheAccessException extends RuntimeException { + CyclicCacheAccessException() {} + } + /** * Special holder class for exceptions that occur during supplier execution. * Allows caching and re-throwing of exceptions on subsequent calls.