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