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.HashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.concurrent.Callable;
30  import java.util.concurrent.CompletionService;
31  import java.util.concurrent.ExecutionException;
32  import java.util.concurrent.ExecutorCompletionService;
33  import java.util.concurrent.ExecutorService;
34  import java.util.concurrent.Executors;
35  import java.util.concurrent.TimeUnit;
36  
37  import org.apache.maven.execution.MavenSession;
38  import org.apache.maven.lifecycle.internal.BuildThreadFactory;
39  import org.apache.maven.lifecycle.internal.LifecycleModuleBuilder;
40  import org.apache.maven.lifecycle.internal.ProjectBuildList;
41  import org.apache.maven.lifecycle.internal.ProjectSegment;
42  import org.apache.maven.lifecycle.internal.ReactorBuildStatus;
43  import org.apache.maven.lifecycle.internal.ReactorContext;
44  import org.apache.maven.lifecycle.internal.TaskSegment;
45  import org.apache.maven.lifecycle.internal.builder.Builder;
46  import org.apache.maven.project.MavenProject;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  /**
51   * Builds the full lifecycle in weave-mode (phase by phase as opposed to project-by-project).
52   * <p>
53   * This builder uses a number of threads equal to the minimum of the degree of concurrency (which is the thread count
54   * set with <code>-T</code> on the command-line) and the number of projects to build. As such, building a single project
55   * will always result in a sequential build, regardless of the thread count.
56   * </p>
57   * <strong>NOTE:</strong> This class is not part of any public api and can be changed or deleted without prior notice.
58   *
59   * @since 3.0
60   * @author Kristian Rosenvold
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  @Singleton
65  @Named("multithreaded")
66  public class MultiThreadedBuilder implements Builder {
67  
68      private final Logger logger = LoggerFactory.getLogger(getClass());
69  
70      @Inject
71      private LifecycleModuleBuilder lifecycleModuleBuilder;
72  
73      public MultiThreadedBuilder() {}
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 
128         // gather artifactIds which are not unique so that the respective thread names can be extended with the groupId
129         Set<String> duplicateArtifactIds = gatherDuplicateArtifactIds(projectBuildList.keySet());
130 
131         // schedule independent projects
132         for (MavenProject mavenProject : analyzer.getRootSchedulableBuilds()) {
133             ProjectSegment projectSegment = projectBuildList.get(mavenProject);
134             logger.debug("Scheduling: " + projectSegment.getProject());
135             Callable<ProjectSegment> cb = createBuildCallable(
136                     rootSession, projectSegment, reactorContext, taskSegment, muxer, duplicateArtifactIds);
137             service.submit(cb);
138         }
139 
140         // for each finished project
141         for (int i = 0; i < analyzer.getNumberOfBuilds(); i++) {
142             try {
143                 ProjectSegment projectBuild = service.take().get();
144                 if (reactorContext.getReactorBuildStatus().isHalted()) {
145                     break;
146                 }
147 
148                 // MNG-6170: Only schedule other modules from reactor if we have more modules to build than one.
149                 if (analyzer.getNumberOfBuilds() > 1) {
150                     final List<MavenProject> newItemsThatCanBeBuilt =
151                             analyzer.markAsFinished(projectBuild.getProject());
152                     for (MavenProject mavenProject : newItemsThatCanBeBuilt) {
153                         ProjectSegment scheduledDependent = projectBuildList.get(mavenProject);
154                         logger.debug("Scheduling: " + scheduledDependent);
155                         Callable<ProjectSegment> cb = createBuildCallable(
156                                 rootSession,
157                                 scheduledDependent,
158                                 reactorContext,
159                                 taskSegment,
160                                 muxer,
161                                 duplicateArtifactIds);
162                         service.submit(cb);
163                     }
164                 }
165             } catch (InterruptedException e) {
166                 rootSession.getResult().addException(e);
167                 break;
168             } catch (ExecutionException e) {
169                 // TODO MNG-5766 changes likely made this redundant
170                 rootSession.getResult().addException(e);
171                 break;
172             }
173         }
174     }
175 
176     private Callable<ProjectSegment> createBuildCallable(
177             final MavenSession rootSession,
178             final ProjectSegment projectBuild,
179             final ReactorContext reactorContext,
180             final TaskSegment taskSegment,
181             final ThreadOutputMuxer muxer,
182             final Set<String> duplicateArtifactIds) {
183         return new Callable<ProjectSegment>() {
184             public ProjectSegment call() {
185                 final Thread currentThread = Thread.currentThread();
186                 final String originalThreadName = currentThread.getName();
187                 final MavenProject project = projectBuild.getProject();
188 
189                 final String threadNameSuffix = duplicateArtifactIds.contains(project.getArtifactId())
190                         ? project.getGroupId() + ":" + project.getArtifactId()
191                         : project.getArtifactId();
192                 currentThread.setName("mvn-builder-" + threadNameSuffix);
193 
194                 try {
195                     // muxer.associateThreadWithProjectSegment( projectBuild );
196                     lifecycleModuleBuilder.buildProject(
197                             projectBuild.getSession(), rootSession, reactorContext, project, taskSegment);
198                     // muxer.setThisModuleComplete( projectBuild );
199 
200                     return projectBuild;
201                 } finally {
202                     currentThread.setName(originalThreadName);
203                 }
204             }
205         };
206     }
207 
208     private Set<String> gatherDuplicateArtifactIds(Set<MavenProject> projects) {
209         Set<String> artifactIds = new HashSet<>(projects.size());
210         Set<String> duplicateArtifactIds = new HashSet<>();
211         for (MavenProject project : projects) {
212             if (!artifactIds.add(project.getArtifactId())) {
213                 duplicateArtifactIds.add(project.getArtifactId());
214             }
215         }
216         return duplicateArtifactIds;
217     }
218 }