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          // Currently disabled
97          ThreadOutputMuxer muxer = null; // new ThreadOutputMuxer( analyzer.getProjectBuilds(), System.out );
98  
99          for (TaskSegment taskSegment : taskSegments) {
100             ProjectBuildList segmentProjectBuilds = projectBuilds.getByTaskSegment(taskSegment);
101             Map<MavenProject, ProjectSegment> projectBuildMap = projectBuilds.selectSegment(taskSegment);
102             try {
103                 ConcurrencyDependencyGraph analyzer =
104                         new ConcurrencyDependencyGraph(segmentProjectBuilds, session.getProjectDependencyGraph());
105                 multiThreadedProjectTaskSegmentBuild(
106                         analyzer, reactorContext, session, service, taskSegment, projectBuildMap, muxer);
107                 if (reactorContext.getReactorBuildStatus().isHalted()) {
108                     break;
109                 }
110             } catch (Exception e) {
111                 session.getResult().addException(e);
112                 break;
113             }
114         }
115 
116         executor.shutdown();
117         executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
118     }
119 
120     private void multiThreadedProjectTaskSegmentBuild(
121             ConcurrencyDependencyGraph analyzer,
122             ReactorContext reactorContext,
123             MavenSession rootSession,
124             CompletionService<ProjectSegment> service,
125             TaskSegment taskSegment,
126             Map<MavenProject, ProjectSegment> projectBuildList,
127             ThreadOutputMuxer muxer) {
128         // gather artifactIds which are not unique so that the respective thread names can be extended with the groupId
129         Set<String> duplicateArtifactIds = projectBuildList.keySet().stream()
130                 .map(MavenProject::getArtifactId)
131                 .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
132                 .entrySet()
133                 .stream()
134                 .filter(p -> p.getValue() > 1)
135                 .map(Map.Entry::getKey)
136                 .collect(Collectors.toSet());
137 
138         // schedule independent projects
139         for (MavenProject mavenProject : analyzer.getRootSchedulableBuilds()) {
140             ProjectSegment projectSegment = projectBuildList.get(mavenProject);
141             logger.debug("Scheduling: {}", projectSegment.getProject());
142             Callable<ProjectSegment> cb = createBuildCallable(
143                     rootSession, projectSegment, reactorContext, taskSegment, muxer, duplicateArtifactIds);
144             service.submit(cb);
145         }
146 
147         // for each finished project
148         for (int i = 0; i < analyzer.getNumberOfBuilds(); i++) {
149             try {
150                 ProjectSegment projectBuild = service.take().get();
151                 if (reactorContext.getReactorBuildStatus().isHalted()) {
152                     break;
153                 }
154 
155                 // MNG-6170: Only schedule other modules from reactor if we have more modules to build than one.
156                 if (analyzer.getNumberOfBuilds() > 1) {
157                     final List<MavenProject> newItemsThatCanBeBuilt =
158                             analyzer.markAsFinished(projectBuild.getProject());
159                     for (MavenProject mavenProject : newItemsThatCanBeBuilt) {
160                         ProjectSegment scheduledDependent = projectBuildList.get(mavenProject);
161                         logger.debug("Scheduling: {}", scheduledDependent);
162                         Callable<ProjectSegment> cb = createBuildCallable(
163                                 rootSession,
164                                 scheduledDependent,
165                                 reactorContext,
166                                 taskSegment,
167                                 muxer,
168                                 duplicateArtifactIds);
169                         service.submit(cb);
170                     }
171                 }
172             } catch (InterruptedException e) {
173                 rootSession.getResult().addException(e);
174                 break;
175             } catch (ExecutionException e) {
176                 // TODO MNG-5766 changes likely made this redundant
177                 rootSession.getResult().addException(e);
178                 break;
179             }
180         }
181     }
182 
183     private Callable<ProjectSegment> createBuildCallable(
184             final MavenSession rootSession,
185             final ProjectSegment projectBuild,
186             final ReactorContext reactorContext,
187             final TaskSegment taskSegment,
188             final ThreadOutputMuxer muxer,
189             final Set<String> duplicateArtifactIds) {
190         return () -> {
191             final Thread currentThread = Thread.currentThread();
192             final String originalThreadName = currentThread.getName();
193             final MavenProject project = projectBuild.getProject();
194 
195             final String threadNameSuffix = duplicateArtifactIds.contains(project.getArtifactId())
196                     ? project.getGroupId() + ":" + project.getArtifactId()
197                     : project.getArtifactId();
198             currentThread.setName("mvn-builder-" + threadNameSuffix);
199 
200             try {
201                 // muxer.associateThreadWithProjectSegment( projectBuild );
202                 lifecycleModuleBuilder.buildProject(
203                         projectBuild.getSession(), rootSession, reactorContext, project, taskSegment);
204                 // muxer.setThisModuleComplete( projectBuild );
205 
206                 return projectBuild;
207             } finally {
208                 currentThread.setName(originalThreadName);
209             }
210         };
211     }
212 }