Skip to content

Commit

Permalink
[MNG-7156][MNG-7285] Add locking in MojoExecutor (#627)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet authored Dec 17, 2021
1 parent 16dd31a commit 33aedfc
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import javax.inject.Inject;
import javax.inject.Named;
Expand All @@ -50,6 +55,7 @@
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.StringUtils;
import org.eclipse.aether.SessionData;

/**
* <p>
Expand All @@ -72,6 +78,8 @@ public class MojoExecutor
private final LifecycleDependencyResolver lifeCycleDependencyResolver;
private final ExecutionEventCatapult eventCatapult;

private final ReadWriteLock aggregatorLock = new ReentrantReadWriteLock();

@Inject
public MojoExecutor(
BuildPluginManager pluginManager,
Expand Down Expand Up @@ -202,6 +210,81 @@ private void execute( MavenSession session, MojoExecution mojoExecution, Project
}
}

try ( ProjectLock lock = new ProjectLock( session, mojoDescriptor, aggregatorLock ) )
{
doExecute( session, mojoExecution, projectIndex, dependencyContext );
}
}

/**
* Aggregating mojo executions (possibly) modify all MavenProjects, including those that are currently in use
* by concurrently running mojo executions. To prevent race conditions, an aggregating execution will block
* all other executions until finished.
* We also lock on a given project to forbid a forked lifecycle to be executed concurrently with the project.
* TODO: ideally, the builder should take care of the ordering in a smarter way
* TODO: and concurrency issues fixed with MNG-7157
*/
private static class ProjectLock implements AutoCloseable
{
final Lock acquiredAggregatorLock;
final Lock acquiredProjectLock;

ProjectLock( MavenSession session, MojoDescriptor mojoDescriptor, ReadWriteLock aggregatorLock )
{
if ( session.getRequest().getDegreeOfConcurrency() > 1 )
{
boolean aggregator = mojoDescriptor.isAggregator();
acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock();
acquiredProjectLock = getProjectLock( session );
acquiredAggregatorLock.lock();
acquiredProjectLock.lock();
}
else
{
acquiredAggregatorLock = null;
acquiredProjectLock = null;
}
}

@Override
public void close()
{
// release the lock in the reverse order of the acquisition
if ( acquiredProjectLock != null )
{
acquiredProjectLock.unlock();
}
if ( acquiredAggregatorLock != null )
{
acquiredAggregatorLock.unlock();
}
}

@SuppressWarnings( { "unchecked", "rawtypes" } )
private Lock getProjectLock( MavenSession session )
{
SessionData data = session.getRepositorySession().getData();
// TODO: when resolver 1.7.3 is released, the code below should be changed to
// TODO: Map<MavenProject, Lock> locks = ( Map ) ((Map) data).computeIfAbsent(
// TODO: ProjectLock.class, l -> new ConcurrentHashMap<>() );
Map<MavenProject, Lock> locks = ( Map ) data.get( ProjectLock.class );
// initialize the value if not already done (in case of a concurrent access) to the method
if ( locks == null )
{
// the call to data.set(k, null, v) is effectively a call to data.putIfAbsent(k, v)
data.set( ProjectLock.class, null, new ConcurrentHashMap<>() );
locks = ( Map ) data.get( ProjectLock.class );
}
return locks.computeIfAbsent( session.getCurrentProject(), p -> new ReentrantLock() );
}
}

private void doExecute( MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex,
DependencyContext dependencyContext )
throws LifecycleExecutionException
{
MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();

List<MavenProject> forkedProjects = executeForkedExecutions( mojoExecution, session, projectIndex );

ensureDependenciesAreResolved( mojoDescriptor, session, dependencyContext );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.maven.AbstractCoreMavenComponentTestCase;
import org.apache.maven.execution.MavenSession;
Expand Down Expand Up @@ -127,6 +128,18 @@ public void testResolveDependencies()
assertEquals( 1, results.size() );
MavenProject mavenProject = results.get( 0 ).getProject();
assertEquals( 1, mavenProject.getArtifacts().size() );

final MavenProject project = mavenProject;
final AtomicInteger artifactsResultInAnotherThread = new AtomicInteger();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
artifactsResultInAnotherThread.set(project.getArtifacts().size());
}
});
t.start();
t.join();
assertEquals( project.getArtifacts().size(), artifactsResultInAnotherThread.get() );
}

@Test
Expand Down

0 comments on commit 33aedfc

Please sign in to comment.