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/05/28 21:15:50 UTC

(maven) branch maven-4.0.x-test-fixes updated: Fix re-entrant self-deadlock in CachingSupplier (PR #12166)

This is an automated email from the ASF dual-hosted git repository.

gnodet pushed a commit to branch maven-4.0.x-test-fixes
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/maven-4.0.x-test-fixes by this push:
     new 0ee8ce1f5d Fix re-entrant self-deadlock in CachingSupplier (PR #12166)
0ee8ce1f5d is described below

commit 0ee8ce1f5d53a8728a4e201beb6c0f47ebf00060
Author: Guillaume Nodet <gn...@gmail.com>
AuthorDate: Thu May 28 23:15:41 2026 +0200

    Fix re-entrant self-deadlock in CachingSupplier (PR #12166)
    
    Cherry-pick from fix/identity-hashmap-request-cache branch.
    When requests() stores CachingSuppliers wrapping individualSupplier in
    the session cache, a re-entrant call from the same thread self-deadlocks.
    Track the computing thread and fall back to direct computation on
    re-entrant access.
    
    Co-Authored-By: Claude Opus 4.6 <no...@anthropic.com>
---
 .../maven/impl/cache/AbstractRequestCache.java       |  9 ++++++++-
 .../org/apache/maven/impl/cache/CachingSupplier.java | 20 ++++++++++++++++++++
 2 files changed, 28 insertions(+), 1 deletion(-)

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 0dc5a7d0f0..4d655c7620 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
@@ -60,7 +60,14 @@ public abstract class AbstractRequestCache implements RequestCache {
     @SuppressWarnings("all")
     public <REQ extends Request<?>, REP extends Result<REQ>> REP request(REQ req, Function<REQ, REP> supplier) {
         CachingSupplier<REQ, REP> 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);
+        }
     }
 
     /**
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 d1960b1944..85b2411e01 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<REQ, REP> implements Function<REQ, REP> {
     protected final Function<REQ, REP> supplier;
     protected volatile Object value;
+    // Guarded by synchronized(this) — tracks which thread is currently computing
+    private Thread computingThread;
 
     public CachingSupplier(Function<REQ, REP> 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.