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