-
Notifications
You must be signed in to change notification settings - Fork 2.7k
[MNG-8696] Hide the cache from DefaultDependencyResolverResult constructor #2347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
[MNG-8696] Hide the cache from DefaultDependencyResolverResult constructor #2347
Conversation
…having a PathModularizationCache argument, and add a public constructor without the cache argument for code in other packages that need to instantiate. The public constructor may be temporary, until we decide in the future where to store session-wide cache.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spelling, potential static inline.
impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolver.java
Outdated
Show resolved
Hide resolved
/** | ||
* Creates an initially empty resolver. | ||
*/ | ||
public DefaultDependencyResolver() {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
who need this? will create very fragile object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constructor was already there before, only implicit (when a Java file does not declare any constructor at all). This addition only makes it explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes right, thanks. But is in use? Encounter compile issues, therefore created and sure about usage?
Might not needed before. Want to make sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that it is instantiated via reflection, as the class is annotated with @Named
and @Singleton
from the org.apache.maven.api.di
package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test
delete, rebuild, pass.
It was done there before and it is without we having to make up whats already there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure which change is requested here. Is it to remove the constructor? It was added only for making explicit what was implicit before. So this is mostly for documentation purpose.
Demonstration
Save the following file somewhere:
/**
* A dummy comment.
*/
public class Test {
/**
* Another dummy comment.
*/
public Test() {
}
}
Compile as below with a recent Java version (tested with Java 24). The -Xdoclint
option is for having warnings about the documentation. The -g:none
option is for omitting debugging information, because the line numbers will change in this demo.
javac -g:none -Xdoclint:all Test.java
Compilation succeed with no warning. Save the output file:
mv Test.class Test.bak
Remove the constructor (including its comment) and recompile with the same command. Note that we now get the following warning:
Test.java:4: warning: use of default constructor, which does not provide a comment
public class Test {
^
1 warning
Compare the output files:
diff Test.bak Test.class
They are identical. Making the implicit constructor explicit changes nothing to the compiled code, but is nevertheless recommended according above warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was added only for making explicit what was implicit before
yes we on the same page. Why we make this? Only if we have 1 constructor that is NON default, only then this change is required, to provide implicit default again. This is not true yet, therefore its "boilerplate" as its there, with or without.
This is a change we can see on compiler lvl, or if rare case we use hibernate having the need for non args constructor like in. https://stackoverflow.com/questions/2935826/why-does-hibernate-require-no-argument-then we see this issue on test lvl.
Please provide stacktrace or reproducer proving this code in mandatory by failing build. Assuming removing the constructor will turn out the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes provided sorry. Then lets merge and local build should fail when removed, right?
Then we simply know riving removal PR. it must fail build.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then lets merge and local build should fail when removed, right?
We could make the build fail when the explicit constructor is absent by adding the -Werror
compiler options. But Maven is not ready for that yet. For now, this change is just resolving one documentation warning among the many the Maven would have if the -Xdoclint:all
option was provided.
impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolverResult.java
Outdated
Show resolved
Hide resolved
*/ | ||
public DefaultDependencyResolverResult( | ||
DependencyResolverRequest request, List<Exception> exceptions, Node root, int count) { | ||
this(request, new PathModularizationCache(), exceptions, root, count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes this gives dedication and option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this(request, new PathModularizationCache(), exceptions, root, count); | |
this(request, moduleCache(), exceptions, root, count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The moduleCache()
method is defined in another class. We cannot access that method from here.
@@ -126,7 +141,11 @@ public DependencyResolverResult collect(@Nonnull DependencyResolverRequest reque | |||
final CollectResult result = | |||
session.getRepositorySystem().collectDependencies(systemSession, collectRequest); | |||
return new DefaultDependencyResolverResult( | |||
null, null, result.getExceptions(), session.getNode(result.getRoot(), request.getVerbose()), 0); | |||
null, | |||
moduleCache(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as its dry, and static, inline. Ide shoud give warning on method or constructor that value is always same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure to understand. Inline the call to moduleCache()
? This is uneasy, as that method does lazy instantiation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry wrong assumption. This is called every time as this the only return therefore default.
DependencyResolverRequest request, | ||
PathModularizationCache cache, | ||
List<Exception> exceptions, | ||
Node root, | ||
int count) { | ||
this.request = request; | ||
this.cache = cache; | ||
this.cache = Objects.requireNonNull(cache); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or here
this.cache = Objects.requireNonNull(cache); | |
this.cache = Objects.requireNonNull(moduleCache()); |
one of the suggestions is right. as moduleCache()
is SSOT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The moduleCache()
method is defined in another class. We cannot access that method from here.
However, this is indeed the kind of code that we would have after we clarified how to manage session-wide caches. The moduleCache()
method would be in the class managing those caches. We are just not there yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fwiw, session wide informations can be stored in Session.getData()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doc link.
impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolver.java
Outdated
Show resolved
Hide resolved
impl/maven-impl/src/main/java/org/apache/maven/impl/DefaultDependencyResolver.java
Show resolved
Hide resolved
if (request.getRequestType() == DependencyResolverRequest.RequestType.FLATTEN) { | ||
DefaultDependencyResolverResult flattenResult = new DefaultDependencyResolverResult( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this scope was right. this field concern lives only here. Even could dedicate whole block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change was for removing code duplication. The exact same code was inside the two branches of the if … else
statement, with only different variable names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes see.
Common part can be extracted from 'if'
its same obtain the warning:

@Override
public DependencyResolverResult resolve(DependencyResolverRequest request)
throws DependencyResolverException, ArtifactResolverException {
InternalSession session =
InternalSession.from(nonNull(request, "request").getSession());
try {
DependencyResolverResult collectorResult = collect(request);
DependencyResolverResult result = collectorResult;
List<RemoteRepository> repositories = request.getRepositories() != null
? request.getRepositories()
: request.getProject().isPresent()
? session.getService(ProjectManager.class)
.getRemoteProjectRepositories(
request.getProject().get())
: session.getRemoteRepositories();
if (request.getRequestType()!= DependencyResolverRequest.RequestType.COLLECT) {
List<Node> nodes = flatten(session, collectorResult.getRoot(), request.getPathScope());
DefaultDependencyResolverResult resolverResult = new DefaultDependencyResolverResult(
null, moduleCache(), collectorResult.getExceptions(), collectorResult.getRoot(), nodes.size());
if (request.getRequestType() == DependencyResolverRequest.RequestType.FLATTEN) {
for (Node node : nodes) {
resolverResult.addNode(node);
}
} else {
for (Node node : nodes) {
Path path = (node.getArtifact() != null)
? session.getService(ArtifactResolver.class).resolve(session, nodes.stream()
.map(Node::getDependency)
.filter(Objects::nonNull)
.map(Artifact::toCoordinates)
.collect(Collectors.toList()), repositories)
.getResult(node.getArtifact().toCoordinates())
.getPath()
: null;
try {
resolverResult.addDependency(node, node.getDependency(), request.getPathTypeFilter(), path);
} catch (IOException e) {
throw cannotReadModuleInfo(path, e);
}
}
}
result = resolverResult;
}
return result;
} finally {
RequestTraceHelper.exit(RequestTraceHelper.enter(session, request));
}
}
is the finally an issue inlining exit ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the finally an issue inlining exit ?
Judging by the method names, it would probably not work. RequestTraceHelper.enter
probably needs to be invoked before to start the execution of the code inside the try
block. Inlining would cause enter
to be invoked too late.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thx.
56f483e
to
f878a62
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
quest.
if (request.getRequestType() == DependencyResolverRequest.RequestType.FLATTEN) { | ||
DefaultDependencyResolverResult flattenResult = new DefaultDependencyResolverResult( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes see.
Common part can be extracted from 'if'
its same obtain the warning:

@Override
public DependencyResolverResult resolve(DependencyResolverRequest request)
throws DependencyResolverException, ArtifactResolverException {
InternalSession session =
InternalSession.from(nonNull(request, "request").getSession());
try {
DependencyResolverResult collectorResult = collect(request);
DependencyResolverResult result = collectorResult;
List<RemoteRepository> repositories = request.getRepositories() != null
? request.getRepositories()
: request.getProject().isPresent()
? session.getService(ProjectManager.class)
.getRemoteProjectRepositories(
request.getProject().get())
: session.getRemoteRepositories();
if (request.getRequestType()!= DependencyResolverRequest.RequestType.COLLECT) {
List<Node> nodes = flatten(session, collectorResult.getRoot(), request.getPathScope());
DefaultDependencyResolverResult resolverResult = new DefaultDependencyResolverResult(
null, moduleCache(), collectorResult.getExceptions(), collectorResult.getRoot(), nodes.size());
if (request.getRequestType() == DependencyResolverRequest.RequestType.FLATTEN) {
for (Node node : nodes) {
resolverResult.addNode(node);
}
} else {
for (Node node : nodes) {
Path path = (node.getArtifact() != null)
? session.getService(ArtifactResolver.class).resolve(session, nodes.stream()
.map(Node::getDependency)
.filter(Objects::nonNull)
.map(Artifact::toCoordinates)
.collect(Collectors.toList()), repositories)
.getResult(node.getArtifact().toCoordinates())
.getPath()
: null;
try {
resolverResult.addDependency(node, node.getDependency(), request.getPathTypeFilter(), path);
} catch (IOException e) {
throw cannotReadModuleInfo(path, e);
}
}
}
result = resolverResult;
}
return result;
} finally {
RequestTraceHelper.exit(RequestTraceHelper.enter(session, request));
}
}
is the finally an issue inlining exit ?
* | ||
* @see #moduleCache() | ||
*/ | ||
private PathModularizationCache moduleCache; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
except of static this is the same outcome.
private PathModularizationCache moduleCache; | |
private final static PathModularizationCache MODULE_CACHE = new PathModularizationCache(); |
@@ -126,7 +141,11 @@ public DependencyResolverResult collect(@Nonnull DependencyResolverRequest reque | |||
final CollectResult result = | |||
session.getRepositorySystem().collectDependencies(systemSession, collectRequest); | |||
return new DefaultDependencyResolverResult( | |||
null, null, result.getExceptions(), session.getNode(result.getRoot(), request.getVerbose()), 0); | |||
null, | |||
moduleCache(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry wrong assumption. This is called every time as this the only return therefore default.
/** | ||
* {@return the cache of information about the modules contained in a path element}. | ||
* | ||
* <p><b>TODO:</b> This method should not be in this class, because the cache should be global to the session. | ||
* This method exists here only temporarily, until clarified where to store session-wide caches.</p> | ||
*/ | ||
private PathModularizationCache moduleCache() { | ||
if (moduleCache == null) { | ||
moduleCache = new PathModularizationCache(); | ||
} | ||
return moduleCache; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** | |
* {@return the cache of information about the modules contained in a path element}. | |
* | |
* <p><b>TODO:</b> This method should not be in this class, because the cache should be global to the session. | |
* This method exists here only temporarily, until clarified where to store session-wide caches.</p> | |
*/ | |
private PathModularizationCache moduleCache() { | |
if (moduleCache == null) { | |
moduleCache = new PathModularizationCache(); | |
} | |
return moduleCache; | |
} |
making field final is the same result. static would change to all classes change same cache. If no collision risk then might be possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A collision is possible if a JAR file changes between two executions. It may happen in particular if a JAR is the output of another project. It can also be a memory leak if a Maven process runs for a long time (the daemon?) and JAR paths change often (e.g. snapshot with timestamps). This is why the desired lifetime is a session (e.g. a call to mvn compile
), with a fresh cache rebuilt every time that mvn compile
is executed from the root of a multi-projects.
@@ -68,6 +68,22 @@ | |||
@Singleton | |||
public class DefaultDependencyResolver implements DependencyResolver { | |||
|
|||
/** | |||
* Cache of information about the modules contained in a path element. | |||
* This cache is created when first needed. It may be never created. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imho no, as always have return call, on default.
Right. Checking again, a see no code path where the cache would not be created. I will remove the lazy instantiation aspect.
@desruisseaux for consistency and to honor the fact that the {{DepfaultDependencyResolverResult}} implements {{DependencyResolverResult}} which is annotated with {{@immutable}}, I'd rather have {{DepfaultDependencyResolverResult}} being immutable. Is there a way to use a temporary intermediate class to store the computation data, and only keep the final computed state in this class ? |
Yes, ideally the cache should be in some session-wide place. The only reason why the cache is declared in Regarding |
- PathModularizationCache is always required, no need for lazy instantiation. - DefaultDependencyResolverResult returns immutable views of collections.
This is an alternative to pull request #2219 resolving the same issue, but with more emphasis that
PathModularizationCache
should not be created in theDefaultDependencyResolverResult
constructor, and that doing so is a temporary hack that may be removed in a future version. More specifically:DefaultDependencyResolverResult
constructor having aPathModularizationCache
argument, because the latter is package-private.DefaultDependencyResolverResult
directly.PathModularizationCache
private field inDefaultDependencyResolver
. This is partially for performance reasons (see below), but also for expressing the intend that the cache has a longer lifetime thanDependencyResolverResult
.Rational
Initializing
PathModularizationCache
inside theDependencyResolverResult
constructor is close to useless, because a cache is useful only when the cached values are reused. The way that theDependencyResolverResult
is coded, the same result instance is unlikely to ask the same cached value twice. The cache become useful only when manyDependencyResolverResult
are instantiated while reusing the same cache.The "useless" approach is nevertheless used by
DependencyResolverResult
, but this is only because I do not yet know how to manage a session-wide cache. The current way to create the cache was intended to be temporary.I'm not sure if it happens often that the same
DependencyResolverResultBuilder
is reused for creating more than oneDependencyResolverResult
. If not, moving the cache insideDependencyResolverResultBuilder
, as done in this commit, may not bring real performance improvement. However, it makes clearer thatPathModularizationCache
is expected to have a longer lifetime thanDependencyResolverResult
. The discussion in #2219 gives the impression that such clarification is worth to do.