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