Skip to content

Commit

Permalink
Introduce skipper to avoid unnecessary dependency resolution
Browse files Browse the repository at this point in the history
  • Loading branch information
caiwei-ebay committed Mar 8, 2022
1 parent 69aeda6 commit e0fa3bc
Show file tree
Hide file tree
Showing 4 changed files with 542 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,9 @@ public CollectResult collectDependencies( RepositorySystemSession session, Colle

DefaultVersionFilterContext versionContext = new DefaultVersionFilterContext( session );

Args args = new Args( session, trace, pool, context, versionContext, request );
Args args =
new Args( session, trace, pool, context, versionContext, request,
new DependencyResolutionSkipper() );
Results results = new Results( result, session );

DependencySelector rootDepSelector =
Expand All @@ -277,6 +279,7 @@ public CollectResult collectDependencies( RepositorySystemSession session, Colle
false );
}

args.skipper.report();
errorPath = results.errorPath;
}

Expand Down Expand Up @@ -396,6 +399,8 @@ private void processDependency( Args args, Results results, DependencyProcessing
return;
}

//Resolve newer version first to maximize benefits of skipper
Collections.reverse( versions );
for ( Version version : versions )
{
Artifact originalArtifact = dependency.getArtifact().setVersion( version.toString() );
Expand Down Expand Up @@ -500,15 +505,19 @@ private void doRecurse( Args args, DependencyProcessingContext parentContext,
List<DependencyNode> children = args.pool.getChildren( key );
if ( children == null )
{
args.pool.putChildren( key, child.getChildren() );

List<DependencyNode> parents = new ArrayList<>( parentContext.parents );
parents.add( child );
for ( Dependency dependency : descriptorResult.getDependencies() )
boolean skipResolve = args.skipper.skipResolution( child, parents );
if ( !skipResolve )
{
args.dependencyProcessingQueue.add(
new DependencyProcessingContext( childSelector, childManager, childTraverser, childFilter,
childRepos, descriptorResult.getManagedDependencies(), parents, dependency ) );
parents.add( child );
for ( Dependency dependency : descriptorResult.getDependencies() )
{
args.dependencyProcessingQueue.add(
new DependencyProcessingContext( childSelector, childManager, childTraverser, childFilter,
childRepos, descriptorResult.getManagedDependencies(), parents, dependency ) );
}
args.pool.putChildren( key, child.getChildren() );
args.skipper.cacheWithDepth( child, parents );
}
}
else
Expand Down Expand Up @@ -697,9 +706,11 @@ static class Args

final CollectRequest request;

final DependencyResolutionSkipper skipper;

Args( RepositorySystemSession session, RequestTrace trace, DataPool pool,
DefaultDependencyCollectionContext collectionContext, DefaultVersionFilterContext versionContext,
CollectRequest request )
CollectRequest request, DependencyResolutionSkipper skipper )
{
this.session = session;
this.request = request;
Expand All @@ -709,6 +720,7 @@ static class Args
this.pool = pool;
this.collectionContext = collectionContext;
this.versionContext = versionContext;
this.skipper = skipper;
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package org.eclipse.aether.internal.impl.collect;

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.graph.DependencyNode;
import org.eclipse.aether.util.artifact.ArtifactIdUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

final class DependencyResolutionSkipper
{
private static final Logger LOGGER = LoggerFactory.getLogger( DependencyResolutionSkipper.class );

private Map<DependencyNode, DependencyResolutionResult> results = new LinkedHashMap<>( 256 );
private CacheManager cacheManager = new CacheManager();
private CoordinateManager coordinateManager = new CoordinateManager();

DependencyResolutionSkipper()
{
// enables default constructor
}

void report()
{
if ( LOGGER.isTraceEnabled() )
{
LOGGER.trace( "Skipped {} nodes as having deeper depth",
results.entrySet().stream().filter( n -> n.getValue().skippedAsDeeperDepth ).count() );
LOGGER.trace( "Skipped {} nodes as having version conflict",
results.entrySet().stream().filter( n -> n.getValue().skippedAsVersionConflict ).count() );
LOGGER.trace( "Resolved {} nodes",
results.entrySet().stream().filter( n -> n.getValue().resolve ).count() );
LOGGER.trace( "Forced resolving {} nodes for scope selection",
results.entrySet().stream().filter( n -> n.getValue().forceResolution ).count() );
}
}

public Map<DependencyNode, DependencyResolutionResult> getResults()
{
return results;
}

boolean skipResolution( DependencyNode node, List<DependencyNode> parents )
{
DependencyResolutionResult result = new DependencyResolutionResult( node );
results.put( node, result );

int depth = parents.size() + 1;
coordinateManager.createCoordinate( node, depth );

if ( cacheManager.isVersionConflict( node ) )
{
//skip resolving version conflict losers (omitted for conflict)
result.skippedAsVersionConflict = true;
if ( LOGGER.isTraceEnabled() )
{
LOGGER.trace( "Skipped resolving node: {} as version conflict",
ArtifactIdUtils.toId( node.getArtifact() ) );
}
}
else if ( coordinateManager.isLeftmost( node, parents ) )
{
/*
* Force resolving the node to retain conflict paths when its coordinate is more left than last resolved.
* This is because Maven picks the widest scope present among conflicting dependencies.
*/
result.forceResolution = true;
if ( LOGGER.isTraceEnabled() )
{
LOGGER.trace( "Force resolving node: {} for scope selection",
ArtifactIdUtils.toId( node.getArtifact() ) );
}
}
else if ( cacheManager.getWinnerDepth( node ) <= depth )
{
//skip resolving if depth deeper (omitted for duplicate)
result.skippedAsDeeperDepth = true;
if ( LOGGER.isTraceEnabled() )
{
LOGGER.trace( "Skipped resolving node: {} as the node's depth is deeper than winner",
ArtifactIdUtils.toId( node.getArtifact() ) );
}
}
else
{
result.resolve = true;
if ( LOGGER.isTraceEnabled() )
{
LOGGER.trace( "Resolving node: {}",
ArtifactIdUtils.toId( node.getArtifact() ) );
}
}

if ( result.toResolve() )
{
coordinateManager.updateLeftmost( node );
return false;
}

return true;
}

void cacheWithDepth( DependencyNode node, List<DependencyNode> parents )
{
boolean parentForceResolution = parents.stream()
.anyMatch( n -> results.containsKey( n ) && results.get( n ).forceResolution );
if ( parentForceResolution )
{
if ( LOGGER.isTraceEnabled() )
{
LOGGER.trace(
"Won't cache as node: {} inherits from a force-resolved node and will be omitted for duplicate",
ArtifactIdUtils.toId( node.getArtifact() ) );
}
}
else
{
cacheManager.cacheWinnerDepth( node, parents.size() + 1 );
}
}


static final class DependencyResolutionResult
{
DependencyNode current;
boolean skippedAsVersionConflict; //omitted for conflict
boolean skippedAsDeeperDepth; //omitted for duplicate
boolean resolve; //node to resolve (winner node)
boolean forceResolution; //force resolving (duplicate node) for scope selection

DependencyResolutionResult( DependencyNode current )
{
this.current = current;
}

boolean toResolve()
{
return resolve || forceResolution;
}
}

static final class CacheManager
{

/**
* artifact -> depth, only cache winners.
*/
private final HashMap<Artifact, Integer> winnerDepths = new HashMap<>( 256 );


/**
* versionLessId -> Artifact, only cache winners
*/
private final Map<String, Artifact> winnerGAs = new HashMap<>( 256 );

boolean isVersionConflict( DependencyNode node )
{
String ga = ArtifactIdUtils.toVersionlessId( node.getArtifact() );
if ( winnerGAs.containsKey( ga ) )
{
Artifact result = winnerGAs.get( ga );
return !node.getArtifact().getVersion().equals( result.getVersion() );
}

return false;
}

void cacheWinnerDepth( DependencyNode node, int depth )
{
LOGGER.trace( "Artifact {} with depth {} cached", node.getArtifact(), depth );
winnerDepths.put( node.getArtifact(), depth );
winnerGAs.put( ArtifactIdUtils.toVersionlessId( node.getArtifact() ), node.getArtifact() );
}

int getWinnerDepth( DependencyNode node )
{
return winnerDepths.getOrDefault( node.getArtifact(), Integer.MAX_VALUE );
}

}


static final class CoordinateManager
{
private final Map<Integer, AtomicInteger> sequenceGen = new HashMap<>( 256 );

/**
* Dependency node -> Coordinate
*/
private final Map<DependencyNode, Coordinate> coordinateMap = new HashMap<>( 256 );

/**
* Leftmost coordinate of given artifact
*/
private final Map<Artifact, Coordinate> leftmostCoordinates = new HashMap<>( 256 );


Coordinate getCoordinate( DependencyNode node )
{
return coordinateMap.get( node );
}

Coordinate createCoordinate( DependencyNode node, int depth )
{
int seq = sequenceGen.computeIfAbsent( depth, k -> new AtomicInteger() ).incrementAndGet();
Coordinate coordinate = new Coordinate( depth, seq );
coordinateMap.put( node, coordinate );
return coordinate;
}

void updateLeftmost( DependencyNode current )
{
leftmostCoordinates.put( current.getArtifact(), getCoordinate( current ) );
}

boolean isLeftmost( DependencyNode node, List<DependencyNode> parents )
{
Coordinate leftmost = leftmostCoordinates.get( node.getArtifact() );
if ( leftmost != null && leftmost.depth <= parents.size() )
{
DependencyNode sameLevelNode = parents.get( leftmost.depth - 1 );
if ( getCoordinate( sameLevelNode ).sequence < leftmost.sequence )
{
return true;
}
}

return false;
}
}

static final class Coordinate
{
int depth;
int sequence;

Coordinate( int depth, int sequence )
{
this.depth = depth;
this.sequence = sequence;
}

@Override
public String toString()
{
return "{"
+ "depth="
+ depth
+ ", sequence="
+ sequence
+ '}';
}
}


}
Loading

0 comments on commit e0fa3bc

Please sign in to comment.