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