Skip to content

Commit e11b475

Browse files
authored
[MNG-8052] Concurrently lifecycle executor (#1429)
1 parent c948b26 commit e11b475

File tree

18 files changed

+2203
-47
lines changed

18 files changed

+2203
-47
lines changed

api/maven-api-core/src/main/java/org/apache/maven/api/Lifecycle.java

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ public interface Lifecycle extends ExtensibleEnum {
5656
// ======================
5757
String BEFORE = "before:";
5858
String AFTER = "after:";
59+
String AT = "at:";
5960

6061
/**
6162
* Name or identifier of this lifecycle.

maven-core/pom.xml

+8
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ under the License.
9191
<groupId>org.apache.maven</groupId>
9292
<artifactId>maven-api-impl</artifactId>
9393
</dependency>
94+
<dependency>
95+
<groupId>org.apache.maven</groupId>
96+
<artifactId>maven-jline</artifactId>
97+
</dependency>
98+
<dependency>
99+
<groupId>org.apache.maven</groupId>
100+
<artifactId>maven-slf4j-provider</artifactId>
101+
</dependency>
94102
<dependency>
95103
<groupId>org.apache.maven.resolver</groupId>
96104
<artifactId>maven-resolver-api</artifactId>

maven-core/src/main/java/org/apache/maven/execution/BuildFailure.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,19 @@ public class BuildFailure extends BuildSummary {
3939
* @param cause The cause of the build failure, may be {@code null}.
4040
*/
4141
public BuildFailure(MavenProject project, long time, Throwable cause) {
42-
super(project, time);
42+
this(project, time, time, cause);
43+
}
44+
45+
/**
46+
* Creates a new build summary for the specified project.
47+
*
48+
* @param project The project being summarized, must not be {@code null}.
49+
* @param execTime The exec time of the project in milliseconds.
50+
* @param wallTime The wall time of the project in milliseconds.
51+
* @param cause The cause of the build failure, may be {@code null}.
52+
*/
53+
public BuildFailure(MavenProject project, long execTime, long wallTime, Throwable cause) {
54+
super(project, execTime, wallTime);
4355
this.cause = cause;
4456
}
4557

maven-core/src/main/java/org/apache/maven/execution/BuildSuccess.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@ public class BuildSuccess extends BuildSummary {
3333
* @param time The build time of the project in milliseconds.
3434
*/
3535
public BuildSuccess(MavenProject project, long time) {
36-
super(project, time);
36+
super(project, time, time);
37+
}
38+
39+
/**
40+
* Creates a new build summary for the specified project.
41+
*
42+
* @param project The project being summarized, must not be {@code null}.
43+
* @param wallTime The wall time of the project in milliseconds.
44+
* @param execTime The exec time of the project in milliseconds.
45+
*/
46+
public BuildSuccess(MavenProject project, long wallTime, long execTime) {
47+
super(project, wallTime, execTime);
3748
}
3849
}

maven-core/src/main/java/org/apache/maven/execution/BuildSummary.java

+31-5
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ public abstract class BuildSummary {
3636
/**
3737
* The build time of the project in milliseconds.
3838
*/
39-
private final long time;
39+
private final long wallTime;
40+
41+
/**
42+
* The total amount of time spent for to run mojos in milliseconds.
43+
*/
44+
private final long execTime;
4045

4146
/**
4247
* Creates a new build summary for the specified project.
@@ -45,9 +50,21 @@ public abstract class BuildSummary {
4550
* @param time The build time of the project in milliseconds.
4651
*/
4752
protected BuildSummary(MavenProject project, long time) {
53+
this(project, time, time);
54+
}
55+
56+
/**
57+
* Creates a new build summary for the specified project.
58+
*
59+
* @param project The project being summarized, must not be {@code null}.
60+
* @param execTime The exec time of the project in milliseconds.
61+
* @param wallTime The wall time of the project in milliseconds.
62+
*/
63+
protected BuildSummary(MavenProject project, long execTime, long wallTime) {
4864
this.project = Objects.requireNonNull(project, "project cannot be null");
4965
// TODO Validate for < 0?
50-
this.time = time;
66+
this.execTime = execTime;
67+
this.wallTime = wallTime;
5168
}
5269

5370
/**
@@ -60,11 +77,20 @@ public MavenProject getProject() {
6077
}
6178

6279
/**
63-
* Gets the build time of the project in milliseconds.
80+
* Gets the wall time of the project in milliseconds.
6481
*
65-
* @return The build time of the project in milliseconds.
82+
* @return The wall time of the project in milliseconds.
6683
*/
6784
public long getTime() {
68-
return time;
85+
return execTime;
86+
}
87+
88+
/**
89+
* Gets the exec time of the project in milliseconds.
90+
*
91+
* @return The exec time of the project in milliseconds.
92+
*/
93+
public long getWallTime() {
94+
return wallTime;
6995
}
7096
}

maven-core/src/main/java/org/apache/maven/lifecycle/internal/CompoundProjectExecutionListener.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@
2424
import org.apache.maven.execution.ProjectExecutionListener;
2525
import org.apache.maven.lifecycle.LifecycleExecutionException;
2626

27-
class CompoundProjectExecutionListener implements ProjectExecutionListener {
27+
public class CompoundProjectExecutionListener implements ProjectExecutionListener {
2828
private final Collection<ProjectExecutionListener> listeners;
2929

30-
CompoundProjectExecutionListener(Collection<ProjectExecutionListener> listeners) {
30+
public CompoundProjectExecutionListener(Collection<ProjectExecutionListener> listeners) {
3131
this.listeners = listeners; // NB this is live injected collection
3232
}
3333

maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java

+53-38
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,13 @@ private void execute(MavenSession session, MojoExecution mojoExecution, Dependen
214214
doExecute(session, mojoExecution, dependencyContext);
215215
}
216216

217+
protected static class NoLock implements NoExceptionCloseable {
218+
public NoLock() {}
219+
220+
@Override
221+
public void close() {}
222+
}
223+
217224
/**
218225
* Aggregating mojo executions (possibly) modify all MavenProjects, including those that are currently in use
219226
* by concurrently running mojo executions. To prevent race conditions, an aggregating execution will block
@@ -222,54 +229,45 @@ private void execute(MavenSession session, MojoExecution mojoExecution, Dependen
222229
* TODO: ideally, the builder should take care of the ordering in a smarter way
223230
* TODO: and concurrency issues fixed with MNG-7157
224231
*/
225-
private class ProjectLock implements AutoCloseable {
232+
protected class ProjectLock implements NoExceptionCloseable {
226233
final Lock acquiredAggregatorLock;
227234
final OwnerReentrantLock acquiredProjectLock;
228235

229236
ProjectLock(MavenSession session, MojoDescriptor mojoDescriptor) {
230237
mojos.put(Thread.currentThread(), mojoDescriptor);
231-
if (session.getRequest().getDegreeOfConcurrency() > 1) {
232-
boolean aggregator = mojoDescriptor.isAggregator();
233-
acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock();
234-
acquiredProjectLock = getProjectLock(session);
235-
if (!acquiredAggregatorLock.tryLock()) {
236-
Thread owner = aggregatorLock.getOwner();
237-
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
238-
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "An";
239-
String msg = str + " aggregator mojo is already being executed "
240-
+ "in this parallel build, those kind of mojos require exclusive access to "
241-
+ "reactor to prevent race conditions. This mojo execution will be blocked "
242-
+ "until the aggregator mojo is done.";
243-
warn(msg);
244-
acquiredAggregatorLock.lock();
245-
}
246-
if (!acquiredProjectLock.tryLock()) {
247-
Thread owner = acquiredProjectLock.getOwner();
248-
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
249-
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "A";
250-
String msg = str + " mojo is already being executed "
251-
+ "on the project " + session.getCurrentProject().getGroupId()
252-
+ ":" + session.getCurrentProject().getArtifactId() + ". "
253-
+ "This mojo execution will be blocked "
254-
+ "until the mojo is done.";
255-
warn(msg);
256-
acquiredProjectLock.lock();
257-
}
258-
} else {
259-
acquiredAggregatorLock = null;
260-
acquiredProjectLock = null;
238+
boolean aggregator = mojoDescriptor.isAggregator();
239+
acquiredAggregatorLock = aggregator ? aggregatorLock.writeLock() : aggregatorLock.readLock();
240+
acquiredProjectLock = getProjectLock(session);
241+
if (!acquiredAggregatorLock.tryLock()) {
242+
Thread owner = aggregatorLock.getOwner();
243+
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
244+
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "An";
245+
String msg = str + " aggregator mojo is already being executed "
246+
+ "in this parallel build, those kind of mojos require exclusive access to "
247+
+ "reactor to prevent race conditions. This mojo execution will be blocked "
248+
+ "until the aggregator mojo is done.";
249+
warn(msg);
250+
acquiredAggregatorLock.lock();
251+
}
252+
if (!acquiredProjectLock.tryLock()) {
253+
Thread owner = acquiredProjectLock.getOwner();
254+
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
255+
String str = ownerMojo != null ? " The " + ownerMojo.getId() : "A";
256+
String msg = str + " mojo is already being executed "
257+
+ "on the project " + session.getCurrentProject().getGroupId()
258+
+ ":" + session.getCurrentProject().getArtifactId() + ". "
259+
+ "This mojo execution will be blocked "
260+
+ "until the mojo is done.";
261+
warn(msg);
262+
acquiredProjectLock.lock();
261263
}
262264
}
263265

264266
@Override
265267
public void close() {
266268
// release the lock in the reverse order of the acquisition
267-
if (acquiredProjectLock != null) {
268-
acquiredProjectLock.unlock();
269-
}
270-
if (acquiredAggregatorLock != null) {
271-
acquiredAggregatorLock.unlock();
272-
}
269+
acquiredProjectLock.unlock();
270+
acquiredAggregatorLock.unlock();
273271
mojos.remove(Thread.currentThread());
274272
}
275273

@@ -308,7 +306,7 @@ private void doExecute(MavenSession session, MojoExecution mojoExecution, Depend
308306

309307
ensureDependenciesAreResolved(mojoDescriptor, session, dependencyContext);
310308

311-
try (ProjectLock lock = new ProjectLock(session, mojoDescriptor)) {
309+
try (NoExceptionCloseable lock = getProjectLock(session, mojoDescriptor)) {
312310
doExecute2(session, mojoExecution);
313311
} finally {
314312
for (MavenProject forkedProject : forkedProjects) {
@@ -317,6 +315,23 @@ private void doExecute(MavenSession session, MojoExecution mojoExecution, Depend
317315
}
318316
}
319317

318+
protected interface NoExceptionCloseable extends AutoCloseable {
319+
@Override
320+
void close();
321+
}
322+
323+
protected NoExceptionCloseable getProjectLock(MavenSession session, MojoDescriptor mojoDescriptor) {
324+
if (useProjectLock(session)) {
325+
return new ProjectLock(session, mojoDescriptor);
326+
} else {
327+
return new NoLock();
328+
}
329+
}
330+
331+
protected boolean useProjectLock(MavenSession session) {
332+
return session.getRequest().getDegreeOfConcurrency() > 1;
333+
}
334+
320335
private void doExecute2(MavenSession session, MojoExecution mojoExecution) throws LifecycleExecutionException {
321336
eventCatapult.fire(ExecutionEvent.Type.MojoStarted, session, mojoExecution);
322337
try {

0 commit comments

Comments
 (0)