Skip to content

Commit

Permalink
Concurrently lifecycle executor
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet committed Feb 29, 2024
1 parent 956ee16 commit 02cdc33
Show file tree
Hide file tree
Showing 20 changed files with 2,107 additions and 13 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/maven_build_itself.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,4 @@ jobs:
run: |
set +e
export PATH=${{ env.TEMP_MAVEN_BIN_DIR }}:$PATH
mvn verify site -e -B -V -DdistributionFileName=apache-maven -Preporting
mvn verify site -e -B -V -DdistributionFileName=apache-maven -Preporting -T1
8 changes: 8 additions & 0 deletions maven-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ under the License.
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-spi</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 @@ -156,6 +156,7 @@ private static void addPhase(
phase.phases().forEach(child -> addPhase(graph, ep1, ep2, child));
}

@Named
static class LifecycleWrapperProvider implements LifecycleProvider {
private final Map<String, org.apache.maven.lifecycle.Lifecycle> lifecycles;

Expand Down
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 @@ -242,6 +242,7 @@ private class ProjectLock implements AutoCloseable {
warn(msg);
acquiredAggregatorLock.lock();
}
/*
if (!acquiredProjectLock.tryLock()) {
Thread owner = acquiredProjectLock.getOwner();
MojoDescriptor ownerMojo = owner != null ? mojos.get(owner) : null;
Expand All @@ -254,6 +255,7 @@ private class ProjectLock implements AutoCloseable {
warn(msg);
acquiredProjectLock.lock();
}
*/
} else {
acquiredAggregatorLock = null;
acquiredProjectLock = null;
Expand All @@ -264,7 +266,7 @@ private class ProjectLock implements AutoCloseable {
public void close() {
// release the lock in the reverse order of the acquisition
if (acquiredProjectLock != null) {
acquiredProjectLock.unlock();
// acquiredProjectLock.unlock();
}
if (acquiredAggregatorLock != null) {
acquiredAggregatorLock.unlock();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* 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.
*/
package org.apache.maven.lifecycle.lfv4;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;

public class BuildPlan {

private final Map<MavenProject, Map<String, BuildStep>> plan = new LinkedHashMap<>();
private final Map<MavenProject, List<MavenProject>> projects;
private final Map<String, String> aliases = new HashMap<>();
private volatile Set<String> duplicateIds;
private volatile List<BuildStep> sortedNodes;

BuildPlan() {
this.projects = null;
}

public BuildPlan(Map<MavenProject, List<MavenProject>> projects) {
this.projects = projects;
}

public Map<MavenProject, List<MavenProject>> getAllProjects() {
return projects;
}

public Map<String, String> aliases() {
return aliases;
}

public Stream<MavenProject> projects() {
return plan.keySet().stream();
}

public void addProject(MavenProject project, Map<String, BuildStep> steps) {
plan.put(project, steps);
}

public void addStep(MavenProject project, String name, BuildStep step) {
plan.get(project).put(name, step);
}

public Stream<BuildStep> allSteps() {
return plan.values().stream().flatMap(m -> m.values().stream());
}

public Stream<BuildStep> steps(MavenProject project) {
return Optional.ofNullable(plan.get(project))
.map(m -> m.values().stream())
.orElse(Stream.empty());
}

public Optional<BuildStep> step(MavenProject project, String name) {
return Optional.ofNullable(plan.get(project)).map(m -> m.get(name));
}

public BuildStep requiredStep(MavenProject project, String name) {
return step(project, name).get();
}

// add a follow-up plan to this one
public void then(BuildPlan step) {
step.plan.forEach((k, v) -> plan.merge(k, v, this::merge));
aliases.putAll(step.aliases);
}

private Map<String, BuildStep> merge(Map<String, BuildStep> org, Map<String, BuildStep> add) {
// all new phases should be added after the existing ones
List<BuildStep> lasts =
org.values().stream().filter(b -> b.successors.isEmpty()).collect(Collectors.toList());
List<BuildStep> firsts =
add.values().stream().filter(b -> b.predecessors.isEmpty()).collect(Collectors.toList());
firsts.stream()
.filter(addNode -> !org.containsKey(addNode.name))
.forEach(addNode -> lasts.forEach(orgNode -> addNode.executeAfter(orgNode)));
add.forEach((name, node) -> org.merge(name, node, this::merge));
return org;
}

private BuildStep merge(BuildStep node1, BuildStep node2) {
node1.predecessors.addAll(node2.predecessors);
node1.successors.addAll(node2.successors);
node2.mojos.forEach((k, v) -> node1.mojos.merge(k, v, this::mergeMojos));
return node1;
}

private Map<String, MojoExecution> mergeMojos(Map<String, MojoExecution> l1, Map<String, MojoExecution> l2) {
l2.forEach(l1::putIfAbsent);
return l1;
}

// gather artifactIds which are not unique so that the respective thread names can be extended with the groupId
public Set<String> duplicateIds() {
if (duplicateIds == null) {
synchronized (this) {
if (duplicateIds == null) {
duplicateIds = projects()
.map(MavenProject::getArtifactId)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet()
.stream()
.filter(p -> p.getValue() > 1)
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
}
}
return duplicateIds;
}

public List<BuildStep> sortedNodes() {
if (sortedNodes == null) {
synchronized (this) {
if (sortedNodes == null) {
List<BuildStep> sortedNodes = new ArrayList<>();
Set<BuildStep> visited = new HashSet<>();
// Visit each unvisited node
allSteps().forEach(node -> visitNode(node, visited, sortedNodes));
// Reverse the sorted nodes to get the correct order
Collections.reverse(sortedNodes);
this.sortedNodes = sortedNodes;
}
}
}
return sortedNodes;
}

// Helper method to visit a node
private static void visitNode(BuildStep node, Set<BuildStep> visited, List<BuildStep> sortedNodes) {
if (visited.add(node)) {
// For each successor of the current node, visit unvisited successors
node.successors.forEach(successor -> visitNode(successor, visited, sortedNodes));
sortedNodes.add(node);
}
}
}
Loading

0 comments on commit 02cdc33

Please sign in to comment.