I am just writing at the moment and want to commit that into Ants sandbox
"parallelexecutor" ;)
My basic idea is:
- each target should run in its own thread
- each thread could start if all dependent thread stopped successfully
Ant is relying on Java 1.3, but for better concurrency support I am using Java
1.6.
ATM I am thinking about how to test that ...
Jan
---8-<-------8-<-------8-<-------8-<-------8-<-------8-<-------8-<-------8-<----
package org.apache.ant.parallelexecutor;
import static org.apache.ant.parallelexecutor.TargetContainerStatus.WAITING;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Executor;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
/**
* <p>This executors parallelizes the exution of Ant targets.
* Each target will run in its own thread. That thread will be started
* if all dependend targets are finished, the if/unless attributes are
* evaluated and no dependend target failed. When a TargetContainer finishes
* it calls this Executors <tt>targetFinished</tt> so that the Executor could
* restart all the waiting threads.</p>
* <p>This executor is used via Ants magic property
<tt>ant.executor.class</tt></p>
* <pre>ant
-Dant.executor.class=org.apache.ant.parallelexecutor.ParallelExecutor</pre>
*/
public class ParallelExecutor implements Executor {
/**
* Default value for waiting for shutting down the ExecutorService.
* @see ExecutorService#awaitTermination(long, TimeUnit)
* @see #EXECUTOR_SERVICE_SHUTDOWN_TIME_UNIT
*/
public static final long EXECUTOR_SERVICE_SHUTDOWN_TIME_VALUE = 10;
/**
* TimeUnit for the shutdown time.
*/
public static final TimeUnit EXECUTOR_SERVICE_SHUTDOWN_TIME_UNIT =
TimeUnit.MINUTES;
/**
* Targets which should run, wrapped by TargetContainers for threading and
monitoring.
*/
private Set<TargetContainer> targetsToProcess;
/**
* ExecutorService for Thread-creation.
*/
private ExecutorService executorService;
/**
* Entry-point defined in the Executor interface.
* Initializes this Executor and starts the threads.
* @see
org.apache.tools.ant.Executor#executeTargets(org.apache.tools.ant.Project,
java.lang.String[])
*/
public void executeTargets(Project project, String[] targetNames) throws
BuildException {
targetsToProcess = getTargetsToProcess(project, targetNames);
executorService = java.util.concurrent.Executors.newCachedThreadPool();
startWaitingContainers();
}
/**
* Initializes the list of TargetContainers with all targets which should
be started.
* @param project project containing the targets
* @param targetNames list of the targets to start
* @return list of TargetContainers for these targets
*/
private Set<TargetContainer> getTargetsToProcess(Project project, String[]
targetNames) {
Set<TargetContainer> rv = new HashSet<TargetContainer>();
for (String targetName : targetNames) {
Target target = (Target)project.getTargets().get(targetName);
TargetContainer container = new TargetContainer(target, this);
targetsToProcess.add(container);
}
return rv;
}
/**
* not used
* @see org.apache.tools.ant.Executor#getSubProjectExecutor()
*/
public Executor getSubProjectExecutor() {
return null;
}
/**
* Starts all waiting TargetContainers.
* @return <tt>true</tt> if one or more containers were be started,
<tt>false</tt> otherwise.
*/
private boolean startWaitingContainers() {
boolean hasStartedAContainer = false;
for (TargetContainer container : targetsToProcess) {
if (container.getCurrentStatus() == WAITING) {
executorService.execute(container);
hasStartedAContainer = true;
}
}
return hasStartedAContainer;
}
/**
* Call-back method for finishing TargetContainers.
* @param container TargetContainer which finished.
*/
public void targetFinished(TargetContainer container) {
if (!startWaitingContainers()) {
// no more waiting containers, so we have finished
try {
// ... if there were no running targets ...
executorService.awaitTermination(
EXECUTOR_SERVICE_SHUTDOWN_TIME_VALUE,
EXECUTOR_SERVICE_SHUTDOWN_TIME_UNIT);
} catch (InterruptedException e) {
// no-op
}
buildFinished();
}
}
/**
* Collects all the results from the different targets.
*/
private void buildFinished() {
List<String> runningTargets = new ArrayList<String>();
List<BuildException> thrownExceptions = new ArrayList<BuildException>();
// Checks the targets for BuildExceptions and their state.
for (TargetContainer container : targetsToProcess) {
if (!container.getCurrentStatus().hasFinished()) {
runningTargets.add(container.getName());
}
if (container.getBuildExeption()!=null) {
thrownExceptions.add(container.getBuildExeption());
}
}
if (!runningTargets.isEmpty()) {
// There are still running targets - shouldn't be, so add an error.
StringBuilder sb = new StringBuilder();
sb.append("Shutting down while having running targets: ");
for (int i=0; i<runningTargets.size()-1; i++) {
sb.append(runningTargets.get(i)).append(", ");
}
sb.append(runningTargets.get(runningTargets.size()));
thrownExceptions.add(new BuildException(sb.toString()));
}
// throw BuildExceptions if needed
throwCaughtExceptions(thrownExceptions);
}
/**
* Checks the given list of BuildExceptions and <ol>
* <li>throws a composite BuildException with the information provided by
that list</li>
* <li>throws the BuildException if the list does contain only one
exception</li>
* <li>does not throw any Exception if the list is empty</li>
* </ul>
* @param thrownExceptions list of caught exceptions
*/
private void throwCaughtExceptions(List<BuildException> thrownExceptions) {
if (thrownExceptions.isEmpty()) {
return;
}
if (thrownExceptions.size() == 1) {
throw thrownExceptions.get(0);
}
// Collect all BEs into one new
StringBuilder sb = new StringBuilder();
sb.append("Multiple BuildExceptions occured:")
.append(System.getProperty("line.separator"));
for (BuildException be : thrownExceptions) {
sb.append("\t")
.append(be.getLocation())
.append(" : ")
.append(be.getMessage())
.append(System.getProperty("line.separator"));
}
throw new BuildException(sb.toString());
}
/**
* Returns the current status for a given target.
* @param depName name of the target
* @return status of that target
*/
public TargetContainerStatus getStatus(String depName) {
return getContainer(depName).getCurrentStatus();
}
/**
* Returns a TargetContainer by its name.
* @param targetName name of the target to look for
* @return the target container wrapping that target
*/
private TargetContainer getContainer(String targetName) {
for (TargetContainer container : targetsToProcess) {
if (container.getName().equals(targetName)) {
return container;
}
}
return null;
}
}
---8-<-------8-<-------8-<-------8-<-------8-<-------8-<-------8-<-------8-<----
package org.apache.ant.parallelexecutor;
import java.util.Enumeration;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Target;
import static org.apache.ant.parallelexecutor.TargetContainerStatus.*;
/**
* The TargetContainer wrapps a target for the use in a multithreaded
environment.
* It provides the "management methods" needed by the ParallelExecutor.
*/
public class TargetContainer implements Runnable {
/** Current status of the target. */
private TargetContainerStatus currentStatus;
/** Wrapped target. */
private Target target;
/** Caught exception if the target throws one. */
private BuildException caughtBuildExeption;
/** The calling ParallelExecutor to inform on finished works. */
private ParallelExecutor caller;
/**
* Constructor.
* @param target target to wrap
* @param caller calling exectuor for call back
*/
public TargetContainer(Target target, ParallelExecutor caller) {
this.target = target;
this.caller = caller;
setCurrentStatus(WAITING);
}
/**
* Gets the current status of the TargetContainer.
* @return status
*/
public TargetContainerStatus getCurrentStatus() {
return currentStatus;
}
/**
* Sets the current status of the TargetContainer.
* @param currentStatus new status
*/
private void setCurrentStatus(TargetContainerStatus currentStatus) {
synchronized (this.currentStatus) {
this.currentStatus = currentStatus;
}
}
/**
* Called by the [EMAIL PROTECTED] ParallelExecutor} if the target should
try
* to start.
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
setCurrentStatus(CHECKING);
TargetContainerStatus statusDepends = checkDependentTargets();
if (statusDepends == SUCCESSED) {
if (evaluateIf() && !evaluateUnless()) {
setCurrentStatus(RUNNING);
try {
target.execute();
setCurrentStatus(SUCCESSED);
} catch (BuildException be) {
// Don't handle the be, just store it for handling by the
executor.
caughtBuildExeption = be;
setCurrentStatus(FAILED);
}
} else {
// 'if' or 'unless' attributes failed
setCurrentStatus(PREREQUISITE_FAILED);
}
} else {
// Finishing the run method stops the thread. Status here is
WAITING or PREFAILED
setCurrentStatus(statusDepends);
}
}
/**
* Checks the result of the dependend targets.
* @return <tt>SUCCESSED</tt> if <b>all</b> targets finished successfully,
* <tt>PREREQUISITE_FAILED</tt> if one or more failed or
* <tt>WAITING</tt> otherwise.
*/
private TargetContainerStatus checkDependentTargets() {
for (Enumeration deps = target.getDependencies();
deps.hasMoreElements();) {
TargetContainerStatus status = caller.getStatus((String)
deps.nextElement());
if (status == FAILED || status == PREREQUISITE_FAILED) {
return PREREQUISITE_FAILED;
}
if (status != SUCCESSED) {
return WAITING;
}
}
return SUCCESSED;
}
/**
* Checks if the property specified as <tt>unless</tt> is set.
* @return <tt>true</tt> if set, <tt>false</tt> otherwise
*/
private boolean evaluateUnless() {
return target.getProject().getProperty(target.getUnless()) != null;
}
/**
* Checks if the property specified as <tt>if</tt> is set.
* @return <tt>true</tt> if set, <tt>false</tt> otherwise
*/
private boolean evaluateIf() {
return target.getProject().getProperty(target.getIf()) != null;
}
/**
* Gets the name of the target.
* @return target name
* @see Target#getName()
*/
public String getName() {
return target.getName();
}
/**
* Returns the caught exception thrown by the target.
* @return the exception or <tt>null</tt>
*/
public BuildException getBuildExeption() {
return caughtBuildExeption;
}
}
---8-<-------8-<-------8-<-------8-<-------8-<-------8-<-------8-<-------8-<----
package org.apache.ant.parallelexecutor;
/**
* TargetContainer can be in the here defined states.
* The transistion between them is:<pre>
* start --> WAITING
* WAITING --> CHECKING
* CHECKING --> RUNNING, PREREQUISITE_FAILED
* RUNNING --> SUCCESSED, FAILED
* SUCCESSED --> end
* FAILED --> end
* PREREQUISITE_FAILED --> end
* </pre>
*/
public enum TargetContainerStatus {
/* Waiting for starting by the Executor. */
WAITING,
/* Checking prerequisites (depends, if, unless). */
CHECKING,
/* Target is currently running. */
RUNNING,
/* Target has finished without any error. */
SUCCESSED,
/* Target has thrown a BuildException. */
FAILED,
/* Target could not run because a prerequisite has not succeeded. */
PREREQUISITE_FAILED;
/**
* Utility method for easier access to a set of states.
* <pre>Finished = [SUCCESSED|FAILED|PREREQUISITE_FAILED]</pre>
* @return computed state
*/
public boolean hasFinished() {
return this == SUCCESSED || this == FAILED || this ==
PREREQUISITE_FAILED;
}
}
---8-<-------8-<-------8-<-------8-<-------8-<-------8-<-------8-<-------8-<----
> -----Ursprüngliche Nachricht-----
> Von: Klaus Malorny [mailto:[EMAIL PROTECTED]
> Gesendet: Dienstag, 18. Dezember 2007 10:10
> An: Ant Users List
> Betreff: extended parallelism
>
>
> Hi,
>
> with the increased availability of multi-core systems, I am
> wondering whether
> there are ways to improve the capabilities of Ant to
> parallelize the build
> process. While it is possible to execute tasks in parallel
> within a target, it
> seems to not be simple to execute multiple targets in
> parallel. My vision is
> an extension to the "depends" attribute: if a pipe is used
> instead of a comma,
> the target does not care about the order and the targets may
> be executed in
> parallel by Ant. I would also add parenthesis to group
> things. For example, "(a
> | (b, c)), d)" means, c must be executed after b, but both
> may be executed in
> parallel to a, while d must be executed only after completion
> of a, b and c. Of
> course, more constraints may occur in the dependent targets,
> e.g. if c depends
> on a, they can't be executed in parallel. Just an idea, feel
> free to ignore it ;-)
>
> Klaus
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]
>
>
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]