Author: dfabulich
Date: Fri Nov 6 21:46:48 2009
New Revision: 833566
URL:
http://svn.apache.org/viewvc?rev=833566&view=revLog:
Adding experimental multithreading support. Naive implementation. Not guaranteed to work. Builder beware. You'd be crazy to use this... -Dmaven.threads=4
Modified:
maven/maven-3/trunk/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java
maven/maven-3/trunk/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java
Modified: maven/maven-3/trunk/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java
URL:
http://svn.apache.org/viewvc/maven/maven-3/trunk/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java?rev=833566&r1=833565&r2=833566&view=diff==============================================================================
--- maven/maven-3/trunk/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java (original)
+++ maven/maven-3/trunk/maven-core/src/main/java/org/apache/maven/execution/MavenSession.java Fri Nov 6 21:46:48 2009
@@ -43,7 +43,7 @@
* @author Jason van Zyl
* @version $Id$
*/
-public class MavenSession
+public class MavenSession implements Cloneable
{
private PlexusContainer container;
@@ -318,6 +318,11 @@
{
this.projectDependencyGraph = projectDependencyGraph;
}
+
+ public ProjectDependencyGraph getProjectDependencyGraph()
+ {
+ return projectDependencyGraph;
+ }
public String getReactorFailureBehavior()
{
@@ -361,4 +366,27 @@
return request.getStartTime();
}
+ @Override
+ public MavenSession clone()
+ {
+ try
+ {
+ return (MavenSession) super.clone();
+ }
+ catch ( CloneNotSupportedException e )
+ {
+ throw new RuntimeException("Bug", e);
+ }
+ }
+
+ public void merge( MavenSession session )
+ {
+ if ( session.blackListedProjects != null )
+ {
+ for ( String projectId : session.blackListedProjects )
+ {
+ blackListedProjects.add( projectId );
+ }
+ }
+ }
}
Modified: maven/maven-3/trunk/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java
URL:
http://svn.apache.org/viewvc/maven/maven-3/trunk/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java?rev=833566&r1=833565&r2=833566&view=diff==============================================================================
--- maven/maven-3/trunk/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java (original)
+++ maven/maven-3/trunk/maven-core/src/main/java/org/apache/maven/lifecycle/DefaultLifecycleExecutor.java Fri Nov 6 21:46:48 2009
@@ -31,6 +31,12 @@
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionService;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorCompletionService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
import org.apache.maven.ProjectDependenciesResolver;
import org.apache.maven.artifact.Artifact;
@@ -48,10 +54,12 @@
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.execution.MavenSession;
+import org.apache.maven.execution.ProjectDependencyGraph;
import org.apache.maven.lifecycle.mapping.LifecycleMapping;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
+import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.InvalidPluginDescriptorException;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
@@ -59,7 +67,6 @@
import org.apache.maven.plugin.MojoNotFoundException;
import org.apache.maven.plugin.PluginConfigurationException;
import org.apache.maven.plugin.PluginDescriptorParsingException;
-import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.PluginManagerException;
import org.apache.maven.plugin.PluginNotFoundException;
import org.apache.maven.plugin.PluginResolutionException;
@@ -252,116 +259,242 @@
}
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
+
+ HashMap<MavenProject, ProjectBuild> projectBuildMap = new HashMap<MavenProject, ProjectBuild>();
+ for (ProjectBuild projectBuild : projectBuilds ) {
+ projectBuildMap.put( projectBuild.project, projectBuild );
+ }
- for ( ProjectBuild projectBuild : projectBuilds )
+ ProjectDependencyGraph pdg = session.getProjectDependencyGraph();
+
+ int threadCount = 1;
+ String threadCountProperty = (String) session.getUserProperties().get( "maven.threads.experimental" );
+ if (threadCountProperty != null) {
+ try {
+ threadCount = Integer.parseInt( threadCountProperty );
+ } catch (NumberFormatException e) {
+ logger.warn( "Couldn't parse thread count, will default to 1: " + threadCountProperty );
+ }
+ }
+ if ( logger.isDebugEnabled() )
{
- MavenProject currentProject = projectBuild.project;
-
- long buildStartTime = System.currentTimeMillis();
-
+ logger.debug( "Thread pool: " + threadCount );
+ }
+ Executor executor = Executors.newFixedThreadPool( threadCount );
+ CompletionService<IndividualProjectBuildResult> service = new ExecutorCompletionService<IndividualProjectBuildResult>( executor );
+ HashSet<Future<IndividualProjectBuildResult>> futures = new HashSet<Future<IndividualProjectBuildResult>>();
+
+ // schedule independent projects
+ for (ProjectBuild projectBuild : projectBuilds) {
+ if ( pdg.getUpstreamProjects( projectBuild.project, false ).size() == 0 ) {
+ if ( logger.isDebugEnabled() )
+ {
+ logger.debug( "Scheduling: " + projectBuild.project );
+ }
+ CallableBuilder cb = new CallableBuilder( result, projectBuild, projectIndex, oldContextClassLoader, session );
+ futures.add( service.submit( cb ) );
+ }
+ }
+
+ HashSet<MavenProject> finishedProjects = new HashSet<MavenProject>();
+
+ // for each finished project
+ for (int i = 0; i < projectBuilds.size(); i++) {
+ IndividualProjectBuildResult ipbr;
try
{
- session.setCurrentProject( currentProject );
+ ipbr = service.take().get();
+ }
+ catch ( Exception e )
+ {
+ break;
+ }
+ if (ipbr.shouldHaltBuild) break;
+ ProjectBuild projectBuild = ipbr.projectBuild;
+ MavenProject finishedProject = projectBuild.project;
+ finishedProjects.add( finishedProject );
+ // schedule dependent projects, if all of their requirements are met
+ for ( MavenProject dependentProject : pdg.getDownstreamProjects( finishedProject, false ) ) {
+ boolean allRequirementsMet = true;
+ for ( MavenProject requirement : pdg.getUpstreamProjects( dependentProject, false ) ) {
+ if (!finishedProjects.contains( requirement ) ) {
+ if ( logger.isDebugEnabled() )
+ {
+ logger.debug( "Not scheduling " + dependentProject + " because requirement not met: " + requirement);
+ }
+ allRequirementsMet = false;
+ break;
+ }
+ }
+ if (allRequirementsMet) {
+ ProjectBuild scheduledDependent = projectBuildMap.get( dependentProject );
+ if ( logger.isDebugEnabled() )
+ {
+ logger.debug( "Scheduling: " + dependentProject );
+ }
+ CallableBuilder cb = new CallableBuilder( result, scheduledDependent, projectIndex, oldContextClassLoader, session );
+ futures.add( service.submit( cb ) );
+ }
+ }
+ }
+
+ // cancel outstanding builds (if any)
+ for (Future<IndividualProjectBuildResult> future : futures) {
+ future.cancel( true /* mayInterruptIfRunning */ );
+ }
+
- if ( session.isBlackListed( currentProject ) )
- {
- fireEvent( session, null, LifecycleEventCatapult.PROJECT_SKIPPED );
+ fireEvent( session, null, LifecycleEventCatapult.SESSION_ENDED );
+ }
- continue;
- }
+ private class CallableBuilder implements Callable<IndividualProjectBuildResult> {
- fireEvent( session, null, LifecycleEventCatapult.PROJECT_STARTED );
+ final MavenExecutionResult result;
+ final ProjectBuild projectBuild;
+ final ProjectIndex projectIndex;
+ final ClassLoader oldContextClassLoader;
+ final MavenSession originalSession;
+
+ public CallableBuilder( MavenExecutionResult result, ProjectBuild projectBuild, ProjectIndex projectIndex,
+ ClassLoader oldContextClassLoader, MavenSession originalSession )
+ {
+ this.result = result;
+ this.projectBuild = projectBuild;
+ this.projectIndex = projectIndex;
+ this.oldContextClassLoader = oldContextClassLoader;
+ this.originalSession = originalSession;
+ }
+
+ public IndividualProjectBuildResult call()
+ throws Exception
+ {
+ boolean shouldHaltBuild = buildProject( result, projectBuild, projectIndex, oldContextClassLoader, originalSession );
+ return new IndividualProjectBuildResult( shouldHaltBuild, projectBuild );
+ }
+ }
+
+ private class IndividualProjectBuildResult {
+ public IndividualProjectBuildResult( boolean shouldHaltBuild, ProjectBuild projectBuild )
+ {
+ this.shouldHaltBuild = shouldHaltBuild;
+ this.projectBuild = projectBuild;
+ }
+ final boolean shouldHaltBuild;
+ final ProjectBuild projectBuild;
+
+ }
+
+ private boolean buildProject( MavenExecutionResult result,
+ ProjectBuild projectBuild, ProjectIndex projectIndex,
+ ClassLoader oldContextClassLoader, MavenSession originalSession )
+ {
+ MavenSession session = originalSession.clone();
+ MavenProject currentProject = projectBuild.project;
- ClassRealm projectRealm = currentProject.getClassRealm();
- if ( projectRealm != null )
- {
- Thread.currentThread().setContextClassLoader( projectRealm );
- }
+ long buildStartTime = System.currentTimeMillis();
- MavenExecutionPlan executionPlan =
- calculateExecutionPlan( session, currentProject, projectBuild.taskSegment );
+ try
+ {
+ session.setCurrentProject( currentProject );
- if ( logger.isDebugEnabled() )
- {
- debugProjectPlan( currentProject, executionPlan );
- }
+ if ( session.isBlackListed( currentProject ) )
+ {
+ fireEvent( session, null, LifecycleEventCatapult.PROJECT_SKIPPED );
- // TODO: once we have calculated the build plan then we should accurately be able to download
- // the project dependencies. Having it happen in the plugin manager is a tangled mess. We can optimize
- // this later by looking at the build plan. Would be better to just batch download everything required
- // by the reactor.
+ return false;
+ }
- List<MavenProject> projectsToResolve;
+ fireEvent( session, null, LifecycleEventCatapult.PROJECT_STARTED );
- if ( projectBuild.taskSegment.aggregating )
- {
- projectsToResolve = session.getProjects();
- }
- else
- {
- projectsToResolve = Collections.singletonList( currentProject );
- }
+ ClassRealm projectRealm = currentProject.getClassRealm();
+ if ( projectRealm != null )
+ {
+ Thread.currentThread().setContextClassLoader( projectRealm );
+ }
- for ( MavenProject project : projectsToResolve )
- {
- resolveProjectDependencies( project, executionPlan.getRequiredCollectionScopes(),
- executionPlan.getRequiredResolutionScopes(), session,
- projectBuild.taskSegment.aggregating );
- }
+ MavenExecutionPlan executionPlan =
+ calculateExecutionPlan( session, currentProject, projectBuild.taskSegment );
- DependencyContext dependencyContext =
- new DependencyContext( executionPlan, projectBuild.taskSegment.aggregating );
+ if ( logger.isDebugEnabled() )
+ {
+ debugProjectPlan( currentProject, executionPlan );
+ }
- for ( MojoExecution mojoExecution : executionPlan.getExecutions() )
- {
- execute( session, mojoExecution, projectIndex, dependencyContext );
- }
+ // TODO: once we have calculated the build plan then we should accurately be able to download
+ // the project dependencies. Having it happen in the plugin manager is a tangled mess. We can optimize
+ // this later by looking at the build plan. Would be better to just batch download everything required
+ // by the reactor.
- long buildEndTime = System.currentTimeMillis();
+ List<MavenProject> projectsToResolve;
- result.addBuildSummary( new BuildSuccess( currentProject, buildEndTime - buildStartTime ) );
+ if ( projectBuild.taskSegment.aggregating )
+ {
+ projectsToResolve = session.getProjects();
+ }
+ else
+ {
+ projectsToResolve = Collections.singletonList( currentProject );
+ }
- fireEvent( session, null, LifecycleEventCatapult.PROJECT_SUCCEEDED );
+ for ( MavenProject project : projectsToResolve )
+ {
+ resolveProjectDependencies( project, executionPlan.getRequiredCollectionScopes(),
+ executionPlan.getRequiredResolutionScopes(), session,
+ projectBuild.taskSegment.aggregating );
}
- catch ( Exception e )
+
+ DependencyContext dependencyContext =
+ new DependencyContext( executionPlan, projectBuild.taskSegment.aggregating );
+
+ for ( MojoExecution mojoExecution : executionPlan.getExecutions() )
{
- result.addException( e );
+ execute( session, mojoExecution, projectIndex, dependencyContext );
+ }
- long buildEndTime = System.currentTimeMillis();
+ long buildEndTime = System.currentTimeMillis();
- result.addBuildSummary( new BuildFailure( currentProject, buildEndTime - buildStartTime, e ) );
+ result.addBuildSummary( new BuildSuccess( currentProject, buildEndTime - buildStartTime ) );
- fireEvent( session, null, LifecycleEventCatapult.PROJECT_FAILED );
+ fireEvent( session, null, LifecycleEventCatapult.PROJECT_SUCCEEDED );
+ }
+ catch ( Exception e )
+ {
+ result.addException( e );
- if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( session.getReactorFailureBehavior() ) )
- {
- // continue the build
- }
- else if ( MavenExecutionRequest.REACTOR_FAIL_AT_END.equals( session.getReactorFailureBehavior() ) )
- {
- // continue the build but ban all projects that depend on the failed one
- session.blackList( currentProject );
- }
- else if ( MavenExecutionRequest.REACTOR_FAIL_FAST.equals( session.getReactorFailureBehavior() ) )
- {
- // abort the build
- break;
- }
- else
- {
- throw new IllegalArgumentException( "invalid reactor failure behavior "
- + session.getReactorFailureBehavior() );
- }
+ long buildEndTime = System.currentTimeMillis();
+
+ result.addBuildSummary( new BuildFailure( currentProject, buildEndTime - buildStartTime, e ) );
+
+ fireEvent( session, null, LifecycleEventCatapult.PROJECT_FAILED );
+
+ if ( MavenExecutionRequest.REACTOR_FAIL_NEVER.equals( session.getReactorFailureBehavior() ) )
+ {
+ // continue the build
}
- finally
+ else if ( MavenExecutionRequest.REACTOR_FAIL_AT_END.equals( session.getReactorFailureBehavior() ) )
{
- session.setCurrentProject( null );
-
- Thread.currentThread().setContextClassLoader( oldContextClassLoader );
+ // continue the build but ban all projects that depend on the failed one
+ session.blackList( currentProject );
+ }
+ else if ( MavenExecutionRequest.REACTOR_FAIL_FAST.equals( session.getReactorFailureBehavior() ) )
+ {
+ // abort the build
+ return true;
+ }
+ else
+ {
+ throw new IllegalArgumentException( "invalid reactor failure behavior "
+ + session.getReactorFailureBehavior() );
}
}
+ finally
+ {
+ session.setCurrentProject( null );
+ originalSession.merge( session );
- fireEvent( session, null, LifecycleEventCatapult.SESSION_ENDED );
+ Thread.currentThread().setContextClassLoader( oldContextClassLoader );
+ }
+ return false;
}
private void resolveProjectDependencies( MavenProject project, Collection<String> scopesToCollect,