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