View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.lifecycle.internal.builder.multithreaded;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.concurrent.Callable;
29  import java.util.concurrent.CompletionService;
30  import java.util.concurrent.ExecutionException;
31  import java.util.concurrent.ExecutorCompletionService;
32  import java.util.concurrent.ExecutorService;
33  import java.util.concurrent.Executors;
34  import java.util.concurrent.TimeUnit;
35  import java.util.function.Function;
36  import java.util.stream.Collectors;
37  
38  import org.apache.maven.execution.MavenSession;
39  import org.apache.maven.lifecycle.internal.BuildThreadFactory;
40  import org.apache.maven.lifecycle.internal.LifecycleModuleBuilder;
41  import org.apache.maven.lifecycle.internal.ProjectBuildList;
42  import org.apache.maven.lifecycle.internal.ProjectSegment;
43  import org.apache.maven.lifecycle.internal.ReactorBuildStatus;
44  import org.apache.maven.lifecycle.internal.ReactorContext;
45  import org.apache.maven.lifecycle.internal.TaskSegment;
46  import org.apache.maven.lifecycle.internal.builder.Builder;
47  import org.apache.maven.project.MavenProject;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  /**
52   * Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project).
53   * <p>
54   * This builder uses a number of threads equal to the minimum of the degree of concurrency (which is the thread count
55   * set with <code>-T</code> on the command-line) and the number of projects to build. As such, building a single project
56   * will always result in a sequential build, regardless of the thread count.
57   * </p>
58   * <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
59   *
60   * @since 3.0
61   *         Builds one or more lifecycles for a full module
62   *         NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
63   */
64  @Named("multithreaded")
65  @Singleton
66  public class MultiThreadedBuilder implements Builder {
67      private final Logger logger = LoggerFactory.getLogger(getClass());
68  
69      private final LifecycleModuleBuilder lifecycleModuleBuilder;
70  
71      @Inject
72      public MultiThreadedBuilder(LifecycleModuleBuilder lifecycleModuleBuilder) {
73          this.lifecycleModuleBuilder = lifecycleModuleBuilder;
74      }
75  
76      @Override
77      public void build(
78              MavenSession session,
79              ReactorContext reactorContext,
80              ProjectBuildList projectBuilds,
81              List<TaskSegment> taskSegments,
82              ReactorBuildStatus reactorBuildStatus)
83              throws ExecutionException, InterruptedException {
84          int nThreads = Math.min(
85                  session.getRequest().getDegreeOfConcurrency(),
86                  session.getProjects().size());
87          boolean parallel = nThreads > 1;
88          // Propagate the parallel flag to the root session and all of the cloned sessions in each project segment
89          session.setParallel(parallel);
90          for (ProjectSegment segment : projectBuilds) {
91              segment.getSession().setParallel(parallel);
92          }
93          ExecutorService executor = Executors.newFixedThreadPool(nThreads, new BuildThreadFactory());
94          CompletionService<ProjectSegment> service = new ExecutorCompletionService<>(executor);
95  
96          for (TaskSegment taskSegment : taskSegments) {
97              ProjectBuildList segmentProjectBuilds = projectBuilds.getByTaskSegment(taskSegment);
98              Map<MavenProject, ProjectSegment> projectBuildMap = projectBuilds.selectSegment(taskSegment);
99              try {
100                 ConcurrencyDependencyGraph analyzer =
101                         new ConcurrencyDependencyGraph(segmentProjectBuilds, session.getProjectDependencyGraph());
102                 multiThreadedProjectTaskSegmentBuild(
103                         analyzer, reactorContext, session, service, taskSegment, projectBuildMap);
104                 if (reactorContext.getReactorBuildStatus().isHalted()) {
105                     break;
106                 }
107             } catch (Exception e) {
108                 session.getResult().addException(e);
109                 break;
110             }
111         }
112 
113         executor.shutdown();
114         executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
115     }
116 
117     private void multiThreadedProjectTaskSegmentBuild(
118             ConcurrencyDependencyGraph analyzer,
119             ReactorContext reactorContext,
120             MavenSession rootSession,
121             CompletionService<ProjectSegment> service,
122             TaskSegment taskSegment,
123             Map<MavenProject, ProjectSegment> projectBuildList) {
124         // gather artifactIds which are not unique so that the respective thread names can be extended with the groupId
125         Set<String> duplicateArtifactIds = projectBuildList.keySet().stream()
126                 .map(MavenProject::getArtifactId)
127                 .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
128                 .entrySet()
129                 .stream()
130                 .filter(p -> p.getValue() > 1)
131                 .map(Map.Entry::getKey)
132                 .collect(Collectors.toSet());
133 
134         // schedule independent projects
135         for (MavenProject mavenProject : analyzer.getRootSchedulableBuilds()) {
136             ProjectSegment projectSegment = projectBuildList.get(mavenProject);
137             logger.debug("Scheduling: {}", projectSegment.getProject());
138             Callable<ProjectSegment> cb =
139                     createBuildCallable(rootSession, projectSegment, reactorContext, taskSegment, duplicateArtifactIds);
140             service.submit(cb);
141         }
142 
143         // for each finished project
144         for (int i = 0; i < analyzer.getNumberOfBuilds(); i++) {
145             try {
146                 ProjectSegment projectBuild = service.take().get();
147                 if (reactorContext.getReactorBuildStatus().isHalted()) {
148                     break;
149                 }
150 
151                 // MNG-6170: Only schedule other modules from reactor if we have more modules to build than one.
152                 if (analyzer.getNumberOfBuilds() > 1) {
153                     final List<MavenProject> newItemsThatCanBeBuilt =
154                             analyzer.markAsFinished(projectBuild.getProject());
155                     for (MavenProject mavenProject : newItemsThatCanBeBuilt) {
156                         ProjectSegment scheduledDependent = projectBuildList.get(mavenProject);
157                         logger.debug("Scheduling: {}", scheduledDependent);
158                         Callable<ProjectSegment> cb = createBuildCallable(
159                                 rootSession, scheduledDependent, reactorContext, taskSegment, duplicateArtifactIds);
160                         service.submit(cb);
161                     }
162                 }
163             } catch (InterruptedException e) {
164                 rootSession.getResult().addException(e);
165                 break;
166             } catch (ExecutionException e) {
167                 // TODO MNG-5766 changes likely made this redundant
168                 rootSession.getResult().addException(e);
169                 break;
170             }
171         }
172     }
173 
174     private Callable<ProjectSegment> createBuildCallable(
175             final MavenSession rootSession,
176             final ProjectSegment projectBuild,
177             final ReactorContext reactorContext,
178             final TaskSegment taskSegment,
179             final Set<String> duplicateArtifactIds) {
180         return () -> {
181             final Thread currentThread = Thread.currentThread();
182             final String originalThreadName = currentThread.getName();
183             final MavenProject project = projectBuild.getProject();
184 
185             final String threadNameSuffix = duplicateArtifactIds.contains(project.getArtifactId())
186                     ? project.getGroupId() + ":" + project.getArtifactId()
187                     : project.getArtifactId();
188             currentThread.setName("mvn-builder-" + threadNameSuffix);
189 
190             try {
191                 lifecycleModuleBuilder.buildProject(
192                         projectBuild.getSession(), rootSession, reactorContext, project, taskSegment);
193 
194                 return projectBuild;
195             } finally {
196                 currentThread.setName(originalThreadName);
197             }
198         };
199     }
200 }