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