Fix dependency selector cache regression in DF collector#1941
Merged
cstamas merged 1 commit intoJun 29, 2026
Conversation
ScopeDependencySelector and OptionalDependencySelector always create new instances in deriveChildSelector(), even after their behavior has stabilized. Since depth is part of equals/hashCode, every tree depth gets a unique selector, which makes the GraphKey cache in the DF collector miss on every lookup across depths. This causes exponential node growth in large dependency trees. The old selectors (resolver 1.x) returned 'this' once their state stabilized (ScopeDependencySelector after depth 1, OptionalDependency Selector after depth 2). The AndDependencySelector already optimizes for this: when all child selectors return 'this', it also returns 'this' (reference equality check at line 119). This fix restores that pattern: both selectors now return 'this' once depth >= applyFrom (the point after which behavior no longer changes with depth). For ScopeDependencySelector, the only exception is when depth == applyTo (transition from filtering to accept-all). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@cstamas Please assign appropriate label to PR according to the type of change. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ScopeDependencySelectorandOptionalDependencySelector(inimpl.scopepackage) always create new instances inderiveChildSelector(), incrementingdepthat every level. Sincedepthis part ofequals()/hashCode(), every tree depth produces a unique selector, which makes theDataPool/GraphKeycache 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:
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.selectorpackage) returnedthisfromderiveChildSelector()once their behavior stabilized:ScopeDependencySelector: returnedthisoncetransitive=true(after depth 1)OptionalDependencySelector: returnedthisoncedepth >= 2The
AndDependencySelectoralready optimizes for this pattern (line 119): when all child selectors returnthis(reference equality), theAndDependencySelectoralso returnsthis. This meant the entire composite selector was the same instance at all depths 2+, enabling cache hits in theGraphKey.The new
impl.scopeselectors never returnthis— they always create new instances withdepth + 1, breaking this optimization chain.Fix
Both selectors now return
thisfromderiveChildSelector()oncedepth >= applyFrom(the point after which theirselectDependency()behavior no longer changes with depth). ForScopeDependencySelector, the only exception isdepth == applyTowhere 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