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

cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new 46acf1298 Fix dependency selector cache regression in DF collector 
(#1941)
46acf1298 is described below

commit 46acf1298ec42bdc33544b127e322bd553305156
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Jun 29 21:43:12 2026 +0200

    Fix dependency selector cache regression in DF collector (#1941)
    
    ## Summary
    
    `ScopeDependencySelector` and `OptionalDependencySelector` (in `impl.scope` 
package) always create new instances in `deriveChildSelector()`, incrementing 
`depth` at every level. Since `depth` is part of `equals()`/`hashCode()`, every 
tree depth produces a unique selector, which makes the `DataPool`/`GraphKey` 
cache in the DF dependency collector miss on every lookup across depths.
    
    This causes **exponential node growth** in the DF collector for large 
dependency trees. In a benchmark with a large multi-module project:
    
    | Configuration | Node count |
    |---|---|
    | Resolver 1.9.27 (Maven 3.9.16), DF collector | 1,054,704 |
    | Resolver 2.0.x (Maven 3.10.x), BF collector | 1,915,842 |
    | Resolver 2.0.x (Maven 3.10.x), DF collector | 9,893,981 |
    
    The DF collector's 9.4x node increase is caused by cache misses cascading 
exponentially — each miss triggers full recursion, discovering more nodes that 
also miss the cache.
    
    ## Root cause
    
    The old selectors (resolver 1.x, in `util.graph.selector` package) returned 
`this` from `deriveChildSelector()` once their behavior stabilized:
    
    - `ScopeDependencySelector`: returned `this` once `transitive=true` (after 
depth 1)
    - `OptionalDependencySelector`: returned `this` once `depth >= 2`
    
    The `AndDependencySelector` already optimizes for this pattern (line 119): 
when all child selectors return `this` (reference equality), the 
`AndDependencySelector` also returns `this`. This meant the entire composite 
selector was the **same instance** at all depths 2+, enabling cache hits in the 
`GraphKey`.
    
    The new `impl.scope` selectors never return `this` — they always create new 
instances with `depth + 1`, breaking this optimization chain.
    
    ## Fix
    
    Both selectors now return `this` from `deriveChildSelector()` once `depth 
>= applyFrom` (the point after which their `selectDependency()` behavior no 
longer changes with depth). For `ScopeDependencySelector`, the only exception 
is `depth == applyTo` where behavior transitions from "filter by scope" to 
"accept all".
    
    For the default Maven 3.10.x configuration 
(`ScopeDependencySelector.legacy(null, ["test", "provided"])`), this means the 
selector stabilizes at depth 2, matching the old resolver 1.x behavior.
    
    _Claude Code on behalf of Guillaume Nodet_
---
 .../aether/internal/impl/scope/OptionalDependencySelector.java     | 3 +++
 .../aether/internal/impl/scope/ScopeDependencySelector.java        | 7 ++++---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/scope/OptionalDependencySelector.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/scope/OptionalDependencySelector.java
index 90f74c62e..f0d7c4974 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/scope/OptionalDependencySelector.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/scope/OptionalDependencySelector.java
@@ -103,6 +103,9 @@ public final class OptionalDependencySelector implements 
DependencySelector {
     @SuppressWarnings("unchecked")
     public DependencySelector deriveChildSelector(DependencyCollectionContext 
context) {
         requireNonNull(context, "context cannot be null");
+        if (depth >= applyFrom) {
+            return this;
+        }
         return new OptionalDependencySelector(
                 seed,
                 depth + 1,
diff --git 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/scope/ScopeDependencySelector.java
 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/scope/ScopeDependencySelector.java
index 2f450c946..12dcad605 100644
--- 
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/scope/ScopeDependencySelector.java
+++ 
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/scope/ScopeDependencySelector.java
@@ -141,10 +141,11 @@ public final class ScopeDependencySelector implements 
DependencySelector {
         if (depth == 0 && shiftIfRootNull && context.getDependency() == null) {
             return new ScopeDependencySelector(
                     seed, shiftIfRootNull, depth + 1, applyFrom + 1, applyTo, 
included, excluded);
-        } else {
-            return new ScopeDependencySelector(
-                    seed, shiftIfRootNull, depth + 1, applyFrom, applyTo, 
included, excluded);
         }
+        if (depth >= applyFrom && depth != applyTo) {
+            return this;
+        }
+        return new ScopeDependencySelector(seed, shiftIfRootNull, depth + 1, 
applyFrom, applyTo, included, excluded);
     }
 
     @Override

Reply via email to