Skip to content

Commit

Permalink
[MNG-8052] Concurrently lifecycle executor
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet committed Jul 11, 2024
1 parent d0c9a6b commit 48fde02
Show file tree
Hide file tree
Showing 18 changed files with 2,200 additions and 49 deletions.
8 changes: 8 additions & 0 deletions maven-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ under the License.
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-impl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-jline</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-slf4j-provider</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,19 @@ public class BuildFailure extends BuildSummary {
* @param cause The cause of the build failure, may be {@code null}.
*/
public BuildFailure(MavenProject project, long time, Throwable cause) {
super(project, time);
this(project, time, time, cause);
}

/**
* Creates a new build summary for the specified project.
*
* @param project The project being summarized, must not be {@code null}.
* @param execTime The exec time of the project in milliseconds.
* @param wallTime The wall time of the project in milliseconds.
* @param cause The cause of the build failure, may be {@code null}.
*/
public BuildFailure(MavenProject project, long execTime, long wallTime, Throwable cause) {
super(project, execTime, wallTime);
this.cause = cause;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ public class BuildSuccess extends BuildSummary {
* @param time The build time of the project in milliseconds.
*/
public BuildSuccess(MavenProject project, long time) {
super(project, time);
super(project, time, time);
}

/**
* Creates a new build summary for the specified project.
*
* @param project The project being summarized, must not be {@code null}.
* @param wallTime The wall time of the project in milliseconds.
* @param execTime The exec time of the project in milliseconds.
*/
public BuildSuccess(MavenProject project, long wallTime, long execTime) {
super(project, wallTime, execTime);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ public abstract class BuildSummary {
/**
* The build time of the project in milliseconds.
*/
private final long time;
private final long wallTime;

/**
* The total amount of time spent for to run mojos in milliseconds.
*/
private final long execTime;

/**
* Creates a new build summary for the specified project.
Expand All @@ -45,9 +50,21 @@ public abstract class BuildSummary {
* @param time The build time of the project in milliseconds.
*/
protected BuildSummary(MavenProject project, long time) {
this(project, time, time);
}

/**
* Creates a new build summary for the specified project.
*
* @param project The project being summarized, must not be {@code null}.
* @param execTime The exec time of the project in milliseconds.
* @param wallTime The wall time of the project in milliseconds.
*/
protected BuildSummary(MavenProject project, long execTime, long wallTime) {
this.project = Objects.requireNonNull(project, "project cannot be null");
// TODO Validate for < 0?
this.time = time;
this.execTime = execTime;
this.wallTime = wallTime;
}

/**
Expand All @@ -60,11 +77,20 @@ public MavenProject getProject() {
}

/**
* Gets the build time of the project in milliseconds.
* Gets the wall time of the project in milliseconds.
*
* @return The build time of the project in milliseconds.
* @return The wall time of the project in milliseconds.
*/
public long getTime() {
return time;
return execTime;
}

/**
* Gets the exec time of the project in milliseconds.
*
* @return The exec time of the project in milliseconds.
*/
public long getWallTime() {
return wallTime;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@
import org.apache.maven.execution.ProjectExecutionListener;
import org.apache.maven.lifecycle.LifecycleExecutionException;

class CompoundProjectExecutionListener implements ProjectExecutionListener {
public class CompoundProjectExecutionListener implements ProjectExecutionListener {
private final Collection<ProjectExecutionListener> listeners;

CompoundProjectExecutionListener(Collection<ProjectExecutionListener> listeners) {
public CompoundProjectExecutionListener(Collection<ProjectExecutionListener> listeners) {
this.listeners = listeners; // NB this is live injected collection
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,13 @@ private void execute(MavenSession session, MojoExecution mojoExecution, Dependen
doExecute(session, mojoExecution, dependencyContext);
}

protected static class NoLock implements NoExceptionCloseable {
public NoLock() {}

@Override
public void close() {}
}

/**
* 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
Expand All @@ -221,54 +228,45 @@ private void execute(MavenSession session, MojoExecution mojoExecution, Dependen
* TODO: ideally, the builder should take care of the ordering in a smarter way
* TODO: and concurrency issues fixed with MNG-7157
*/
private class ProjectLock implements AutoCloseable {
protected class ProjectLock implements NoExceptionCloseable {
final Lock acquiredAggregatorLock;
final OwnerReentrantLock acquiredProjectLock;

ProjectLock(MavenSession session, MojoDescriptor mojoDescriptor) {
mojos.put(Thread.currentThread(), mojoDescriptor);
if (session.getRequest().getDegreeOfConcurrency() > 1) {
boolean aggregator = mojoDescriptor.isAggregator();
acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock();
acquiredProjectLock = getProjectLock(session);
if (!acquiredAggregatorLock.tryLock()) {
Thread owner = aggregatorLock.getOwner();
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "An";
String msg = str + " aggregator mojo is already being executed "
+ "in this parallel build, those kind of mojos require exclusive access to "
+ "reactor to prevent race conditions. This mojo execution will be blocked "
+ "until the aggregator mojo is done.";
warn(msg);
acquiredAggregatorLock.lock();
}
if (!acquiredProjectLock.tryLock()) {
Thread owner = acquiredProjectLock.getOwner();
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "A";
String msg = str + " mojo is already being executed "
+ "on the project " + session.getCurrentProject().getGroupId()
+ ":" + session.getCurrentProject().getArtifactId() + ". "
+ "This mojo execution will be blocked "
+ "until the mojo is done.";
warn(msg);
acquiredProjectLock.lock();
}
} else {
acquiredAggregatorLock = null;
acquiredProjectLock = null;
boolean aggregator = mojoDescriptor.isAggregator();
acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock();
acquiredProjectLock = getProjectLock(session);
if (!acquiredAggregatorLock.tryLock()) {
Thread owner = aggregatorLock.getOwner();
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "An";
String msg = str + " aggregator mojo is already being executed "
+ "in this parallel build, those kind of mojos require exclusive access to "
+ "reactor to prevent race conditions. This mojo execution will be blocked "
+ "until the aggregator mojo is done.";
warn(msg);
acquiredAggregatorLock.lock();
}
if (!acquiredProjectLock.tryLock()) {
Thread owner = acquiredProjectLock.getOwner();
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "A";
String msg = str + " mojo is already being executed "
+ "on the project " + session.getCurrentProject().getGroupId()
+ ":" + session.getCurrentProject().getArtifactId() + ". "
+ "This mojo execution will be blocked "
+ "until the mojo is done.";
warn(msg);
acquiredProjectLock.lock();
}
}

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

Expand Down Expand Up @@ -307,7 +305,7 @@ private void doExecute(MavenSession session, MojoExecution mojoExecution, Depend

ensureDependenciesAreResolved(mojoDescriptor, session, dependencyContext);

try (ProjectLock lock = new ProjectLock(session, mojoDescriptor)) {
try (NoExceptionCloseable lock = getProjectLock(session, mojoDescriptor)) {
doExecute2(session, mojoExecution);
} finally {
for (MavenProject forkedProject : forkedProjects) {
Expand All @@ -316,6 +314,23 @@ private void doExecute(MavenSession session, MojoExecution mojoExecution, Depend
}
}

protected interface NoExceptionCloseable extends AutoCloseable {
@Override
void close();
}

protected NoExceptionCloseable getProjectLock(MavenSession session, MojoDescriptor mojoDescriptor) {
if (useProjectLock(session)) {
return new ProjectLock(session, mojoDescriptor);
} else {
return new NoLock();
}
}

protected boolean useProjectLock(MavenSession session) {
return session.getRequest().getDegreeOfConcurrency() > 1;
}

private void doExecute2(MavenSession session, MojoExecution mojoExecution) throws LifecycleExecutionException {
eventCatapult.fire(ExecutionEvent.Type.MojoStarted, session, mojoExecution);
try {
Expand Down
Loading

0 comments on commit 48fde02

Please sign in to comment.