Skip to content
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

Fix discovery of components without metadata in repositories #10996

Merged
merged 19 commits into from Oct 10, 2019

Conversation

jjohannes
Copy link
Contributor

@jjohannes jjohannes commented Oct 9, 2019

For components without metadata (metadataSource = artifact), we need to HEAD an artifact to check if a component exists in a repository. This is done in the metadata download phase in resolution (instead of actually downloading a metadata file).

For this to work consistently, information about dependency artifacts needs to be preserved when we construct the so called ComponentOverrideMetadata. However, there can be multiple dependencies (or dependency constraints) declarations with different dependency artifact information. We basically always took the first definition we found. However, this does not work, if the first definition does not define an artifact at all (which is always true for dependency constraints). So it never worked for all cases. Recent changes in selector sorting (8eb2d60 of #9307 and probably others) have lead to changes in the ordering and now some cases that worked before do not work anymore (#10948).

This PR fixes the issue in general by tracking the first found artifact across all sectors for a module instead of just tracking the first selector.

Note: these changes are only necessary to fix the discovery of components in repositories. The actual retrieving of artifact is done later after resolution. This works (and worked before): All artifacts are retrieved or, if one is missing, an error is thrown.

In addition to the changes required for artifact handling, this PR also improves the handling of 'client modules' and 'changing' status (the other details of ComponentOverrideMetadata).

This is just a shortcut to a final filed in 'dependencyState'.
Sometimes it was used and sometimes 'dependencyState.getDepenedency()'
was used which is always the same value.
This makes this complex code a bit easier to comprehend.
This information needs to be preserved to make sure that an artifact
that acts as metadata for itself (metadataSources = artifact()) is
found during the early resolution phase where we search for modules
in repositories.
Later in the resolution, we already combine all artifacts defined
as 'dependency artifacts' on incoming edges.
If a component does not have metadata, we need at least information
about one artifact early when we look for an artifact (instead of a
metadata file).
Such artifacts can actually be addressed by 'dependency artifacts'
through the 'artifact { }' notation inside a dependency declaration;
or in Gradle Module Metadata.
The order can make a difference in repository selection, when
checking if an artifact-only-component containing the artifacts
exists in a repository. In absence of a metadata file, we initially test
for the existence of one artifact to decide if a repository contains
the corresponding artifact-only-component. This is always the first
artifact in the set (which is internally converted into a list).
This change makes sure that the first artifact is always the same
for a build that does not change.
This test reproduces #10948
and other variations of it.
As stated in the here implemented 'ServerWithExpectations' fixture:
"handlers, as well as failures, need to be thread-safe"

This concrete case was working most of the time since usually there are
not more than one expectations for the same request. But if the
same request is expected several times, and the requests are received
in parallel (as it is the case for metadata download), the handle()
method behaved flaky - by not doing reading and updating of the 'run'
flag as an atomic operation.
@jjohannes jjohannes added this to the 6.0 RC1 milestone Oct 9, 2019
@jjohannes jjohannes self-assigned this Oct 9, 2019
This seems to be dependent on timing. If one request starts after
another did already finish, it can take the result from cache.
@jjohannes jjohannes force-pushed the jjohannes/artifacts-metadata-source-discovery-fix branch from d9f51e7 to 67b02ff Compare October 10, 2019 08:32
public void selectedBy(ResolvableSelectorState selectorState) {
if (firstSelectedBy == null) {
firstSelectedBy = (SelectorState) selectorState;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavior of this changed with the selector sorting. So a gaol of this PR is to get rid of this additional (weird) state and instead rely on the order of the selector list.

@jjohannes jjohannes force-pushed the jjohannes/artifacts-metadata-source-discovery-fix branch from 67b02ff to e43500a Compare October 10, 2019 08:45
Although these are edge cases, it leads to more consistency and makes
the behavior less dependent on order which may change unexpectedly
through internal optimisations.
We now rely on the selector sorting when choosing a selector
to compute override metadata. The sorting puts the most
likely used selector first.
@jjohannes jjohannes force-pushed the jjohannes/artifacts-metadata-source-discovery-fix branch from e43500a to 181205e Compare October 10, 2019 08:55
Copy link
Member

@ljacomet ljacomet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good stuff in there! 👍

Two minor remarks

@@ -96,7 +96,7 @@ public LocalComponentDependencyMetadata(ComponentIdentifier componentId,
}

private static List<IvyArtifactName> asImmutable(List<IvyArtifactName> artifactNames) {
return artifactNames.isEmpty() ? Collections.emptyList() : ImmutableList.copyOf(artifactNames);
return artifactNames.isEmpty() ? Collections.emptyList() : artifactNames instanceof ImmutableList ? artifactNames : ImmutableList.copyOf(artifactNames);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already the case inside ImmutableList.copyOf AFAICT

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I missed something, but it is called copyOf and I think it always creates a copy.

From the implementation:
ImmutableList<E> list = ((ImmutableCollection<E>) elements).asList(); (which in turn does toArray())
And there is no type check for ImmutableList. Maybe the underlying array is not copied (indicated in the Javadoc, but I can't really find that in the code).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

((ImmutableCollection<E>) elements).asList(); on a ImmutableList does return this

@jjohannes jjohannes merged commit 0511e1f into release Oct 10, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants