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  
91          // Currently disabled
92          ThreadOutputMuxer muxer = null; // new ThreadOutputMuxer( analyzer.getProjectBuilds(), System.out );
93  
94          for ( TaskSegment taskSegment : taskSegments )
95          {
96              ProjectBuildList segmentProjectBuilds = projectBuilds.getByTaskSegment( taskSegment );
97              Map<MavenProject, ProjectSegment> projectBuildMap = projectBuilds.selectSegment( taskSegment );
98              try
99              {
100                 ConcurrencyDependencyGraph analyzer =
101                     new ConcurrencyDependencyGraph( segmentProjectBuilds,
102                                                     session.getProjectDependencyGraph() );
103                 multiThreadedProjectTaskSegmentBuild( analyzer, reactorContext, session, service, taskSegment,
104                                                       projectBuildMap, muxer );
105                 if ( reactorContext.getReactorBuildStatus().isHalted() )
106                 {
107                     break;
108                 }
109             }
110             catch ( Exception e )
111             {
112                 session.getResult().addException( e );
113                 break;
114             }
115 
116         }
117     }
118 
119     private void multiThreadedProjectTaskSegmentBuild( ConcurrencyDependencyGraph analyzer,
120                                                        ReactorContext reactorContext, MavenSession rootSession,
121                                                        CompletionService<ProjectSegment> service,
122                                                        TaskSegment taskSegment,
123                                                        Map<MavenProject, ProjectSegment> projectBuildList,
124                                                        ThreadOutputMuxer muxer )
125     {
126 
127         // schedule independent projects
128         for ( MavenProject mavenProject : analyzer.getRootSchedulableBuilds() )
129         {
130             ProjectSegment projectSegment = projectBuildList.get( mavenProject );
131             logger.debug( "Scheduling: " + projectSegment.getProject() );
132             Callable<ProjectSegment> cb =
133                 createBuildCallable( rootSession, projectSegment, reactorContext, taskSegment, muxer );
134             service.submit( cb );
135         }
136 
137         // for each finished project
138         for ( int i = 0; i < analyzer.getNumberOfBuilds(); i++ )
139         {
140             try
141             {
142                 ProjectSegment projectBuild = service.take().get();
143                 if ( reactorContext.getReactorBuildStatus().isHalted() )
144                 {
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                 {
151                     final List<MavenProject> newItemsThatCanBeBuilt =
152                         analyzer.markAsFinished( projectBuild.getProject() );
153                     for ( MavenProject mavenProject : newItemsThatCanBeBuilt )
154                     {
155                         ProjectSegment scheduledDependent = projectBuildList.get( mavenProject );
156                         logger.debug( "Scheduling: " + scheduledDependent );
157                         Callable<ProjectSegment> cb =
158                             createBuildCallable( rootSession, scheduledDependent, reactorContext, taskSegment, muxer );
159                         service.submit( cb );
160                     }
161                 }
162             }
163             catch ( InterruptedException e )
164             {
165                 rootSession.getResult().addException( e );
166                 break;
167             }
168             catch ( ExecutionException e )
169             {
170                 // TODO MNG-5766 changes likely made this redundant
171                 rootSession.getResult().addException( e );
172                 break;
173             }
174         }
175 
176         // cancel outstanding builds (if any) - this can happen if an exception is thrown in above block
177 
178         Future<ProjectSegment> unprocessed;
179         while ( ( unprocessed = service.poll() ) != null )
180         {
181             try
182             {
183                 unprocessed.get();
184             }
185             catch ( InterruptedException | ExecutionException e )
186             {
187                 throw new RuntimeException( e );
188             }
189         }
190     }
191 
192     private Callable<ProjectSegment> createBuildCallable( final MavenSession rootSession,
193                                                           final ProjectSegment projectBuild,
194                                                           final ReactorContext reactorContext,
195                                                           final TaskSegment taskSegment, final ThreadOutputMuxer muxer )
196     {
197         return new Callable<ProjectSegment>()
198         {
199             public ProjectSegment call()
200             {
201                 // muxer.associateThreadWithProjectSegment( projectBuild );
202                 lifecycleModuleBuilder.buildProject( projectBuild.getSession(), rootSession, reactorContext,
203                                                      projectBuild.getProject(), taskSegment );
204                 // muxer.setThisModuleComplete( projectBuild );
205 
206                 return projectBuild;
207             }
208         };
209     }
210 }