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