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

[MNG-8225] Fully concurrent builder for Maven 4 #1429

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public interface Lifecycle extends ExtensibleEnum {
// ======================
String BEFORE = "before:";
String AFTER = "after:";
String AT = "at:";

/**
* Name or identifier of this lifecycle.
Expand Down
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 @@ -214,6 +214,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 @@ -222,54 +229,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 @@ -308,7 +306,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 @@ -317,6 +315,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