You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ant.apache.org by jk...@apache.org on 2009/02/06 13:36:49 UTC

svn commit: r741554 - in /ant/core/trunk: WHATSNEW src/main/org/apache/tools/ant/taskdefs/Redirector.java

Author: jkf
Date: Fri Feb  6 12:36:48 2009
New Revision: 741554

URL: http://svn.apache.org/viewvc?rev=741554&view=rev
Log:
Pr 44544: Deadlock between in, out and err in the redirector.

Modified:
    ant/core/trunk/WHATSNEW
    ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Redirector.java

Modified: ant/core/trunk/WHATSNEW
URL: http://svn.apache.org/viewvc/ant/core/trunk/WHATSNEW?rev=741554&r1=741553&r2=741554&view=diff
==============================================================================
--- ant/core/trunk/WHATSNEW (original)
+++ ant/core/trunk/WHATSNEW Fri Feb  6 12:36:48 2009
@@ -137,6 +137,9 @@
 
 Fixed bugs:
 -----------
+ * Got rid of deadlock between in in, out and err in the Redirector. 
+   Bugzilla Report 44544.
+
  * Caused by AssertionError no longer filtered. Bugzilla report 45631.
  
  * <zip> would sometimes recreate JARs unnecessarily. Bugzilla report 45902.

Modified: ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Redirector.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Redirector.java?rev=741554&r1=741553&r2=741554&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Redirector.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/Redirector.java Fri Feb  6 12:36:48 2009
@@ -47,19 +47,20 @@
 import org.apache.tools.ant.util.KeepAliveOutputStream;
 
 /**
- * The Redirector class manages the setup and connection of
- * input and output redirection for an Ant project component.
- *
+ * The Redirector class manages the setup and connection of input and output
+ * redirection for an Ant project component.
+ * 
  * @since Ant 1.6
  */
 public class Redirector {
     private static final int STREAMPUMPER_WAIT_INTERVAL = 1000;
 
-    private static final String DEFAULT_ENCODING
-        = System.getProperty("file.encoding");
+    private static final String DEFAULT_ENCODING = System
+            .getProperty("file.encoding");
 
     private class PropertyOutputStream extends ByteArrayOutputStream {
         private String property;
+
         private boolean closed = false;
 
         PropertyOutputStream(String property) {
@@ -68,16 +69,18 @@
         }
 
         public void close() throws IOException {
-            if (!closed && !(append && appendProperties)) {
-                setPropertyFromBAOS(this, property);
-                closed = true;
+            synchronized (outMutex) {
+                if (!closed && !(appendOut && appendProperties)) {
+                    setPropertyFromBAOS(this, property);
+                    closed = true;
+                }
             }
         }
     }
 
     /**
-     * The file(s) from which standard input is being taken.
-     * If &gt; 1, files' content will be concatenated in the order received.
+     * The file(s) from which standard input is being taken. If &gt; 1, files'
+     * content will be concatenated in the order received.
      */
     private File[] input;
 
@@ -93,10 +96,10 @@
     private File[] error;
 
     /**
-      * Indicates if standard error should be logged to Ant's log system
-      * rather than the output. This has no effect if standard error is
-      * redirected to a file or property.
-      */
+     * Indicates if standard error should be logged to Ant's log system rather
+     * than the output. This has no effect if standard error is redirected to a
+     * file or property.
+     */
     private boolean logError = false;
 
     /**
@@ -119,13 +122,19 @@
     private String inputString;
 
     /** Flag which indicates if error and output files are to be appended. */
-    private boolean append = false;
+    private boolean appendOut = false;
+
+    private boolean appendErr = false;
 
     /** Flag which indicates that output should be always sent to the log */
-    private boolean alwaysLog = false;
+    private boolean alwaysLogOut = false;
+
+    private boolean alwaysLogErr = false;
 
     /** Flag which indicates whether files should be created even when empty. */
-    private boolean createEmptyFiles = true;
+    private boolean createEmptyFilesOut = true;
+
+    private boolean createEmptyFilesErr = true;
 
     /** The task for which this redirector is working */
     private ProjectComponent managingTask;
@@ -172,10 +181,20 @@
     /** whether to log the inputstring */
     private boolean logInputString = true;
 
+    /** Mutex for in */
+    private Object inMutex = new Object();
+
+    /** Mutex for out */
+    private Object outMutex = new Object();
+
+    /** Mutex for err */
+    private Object errMutex = new Object();
+
     /**
      * Create a redirector instance for the given task
-     *
-     * @param managingTask the task for which the redirector is to work
+     * 
+     * @param managingTask
+     *            the task for which the redirector is to work
      */
     public Redirector(Task managingTask) {
         this((ProjectComponent) managingTask);
@@ -183,9 +202,9 @@
 
     /**
      * Create a redirector instance for the given task
-     *
-     * @param managingTask the project component for which the
-     * redirector is to work
+     * 
+     * @param managingTask
+     *            the project component for which the redirector is to work
      * @since Ant 1.6.3
      */
     public Redirector(ProjectComponent managingTask) {
@@ -194,35 +213,48 @@
 
     /**
      * Set the input to use for the task
-     *
-     * @param input the file from which input is read.
+     * 
+     * @param input
+     *            the file from which input is read.
      */
     public void setInput(File input) {
-        setInput((input == null) ? null : new File[] {input});
+        setInput((input == null) ? null : new File[] { input });
     }
 
     /**
      * Set the input to use for the task
-     *
-     * @param input the files from which input is read.
-     */
-    public synchronized void setInput(File[] input) {
-        this.input = input;
+     * 
+     * @param input
+     *            the files from which input is read.
+     */
+    public void setInput(File[] input) {
+        synchronized (inMutex) {
+            if (input == null) {
+                this.input = null;
+            } else {
+                this.input = input.clone();
+            }
+        }
     }
 
     /**
      * Set the string to use as input
-     *
-     * @param inputString the string which is used as the input source
-     */
-    public synchronized void setInputString(String inputString) {
-        this.inputString = inputString;
+     * 
+     * @param inputString
+     *            the string which is used as the input source
+     */
+    public void setInputString(String inputString) {
+        synchronized (inMutex) {
+            this.inputString = inputString;
+        }
     }
 
     /**
      * Set whether to include the value of the input string in log messages.
      * Defaults to true.
-     * @param logInputString true or false.
+     * 
+     * @param logInputString
+     *            true or false.
      * @since Ant 1.7
      */
     public void setLogInputString(boolean logInputString) {
@@ -231,218 +263,280 @@
 
     /**
      * Set a stream to use as input.
-     *
-     * @param inputStream the stream from which input will be read
+     * 
+     * @param inputStream
+     *            the stream from which input will be read
      * @since Ant 1.6.3
      */
-    /*public*/ void setInputStream(InputStream inputStream) {
-        this.inputStream = inputStream;
+    /* public */void setInputStream(InputStream inputStream) {
+        synchronized (inMutex) {
+            this.inputStream = inputStream;
+        }
     }
 
     /**
      * File the output of the process is redirected to. If error is not
      * redirected, it too will appear in the output
-     *
-     * @param out the file to which output stream is written
+     * 
+     * @param out
+     *            the file to which output stream is written
      */
     public void setOutput(File out) {
-        setOutput((out == null) ? null : new File[] {out});
+        setOutput((out == null) ? null : new File[] { out });
     }
 
     /**
      * Files the output of the process is redirected to. If error is not
      * redirected, it too will appear in the output
-     *
-     * @param out the files to which output stream is written
-     */
-    public synchronized void setOutput(File[] out) {
-        this.out = out;
+     * 
+     * @param out
+     *            the files to which output stream is written
+     */
+    public void setOutput(File[] out) {
+        synchronized (outMutex) {
+            if (out == null) {
+                this.out = null;
+            } else {
+                this.out = out.clone();
+            }
+        }
     }
 
     /**
      * Set the output encoding.
-     *
-     * @param outputEncoding   <code>String</code>.
+     * 
+     * @param outputEncoding
+     *            <code>String</code>.
      */
-    public synchronized void setOutputEncoding(String outputEncoding) {
+    public void setOutputEncoding(String outputEncoding) {
         if (outputEncoding == null) {
             throw new IllegalArgumentException(
-                "outputEncoding must not be null");
-        } else {
+                    "outputEncoding must not be null");
+        }
+        synchronized (outMutex) {
             this.outputEncoding = outputEncoding;
         }
     }
 
     /**
      * Set the error encoding.
-     *
-     * @param errorEncoding   <code>String</code>.
+     * 
+     * @param errorEncoding
+     *            <code>String</code>.
      */
-    public synchronized void setErrorEncoding(String errorEncoding) {
+    public void setErrorEncoding(String errorEncoding) {
         if (errorEncoding == null) {
-            throw new IllegalArgumentException(
-                "errorEncoding must not be null");
-        } else {
+            throw new IllegalArgumentException("errorEncoding must not be null");
+        }
+        synchronized (errMutex) {
             this.errorEncoding = errorEncoding;
         }
     }
 
     /**
      * Set the input encoding.
-     *
-     * @param inputEncoding   <code>String</code>.
+     * 
+     * @param inputEncoding
+     *            <code>String</code>.
      */
-    public synchronized void setInputEncoding(String inputEncoding) {
+    public void setInputEncoding(String inputEncoding) {
         if (inputEncoding == null) {
-            throw new IllegalArgumentException(
-                "inputEncoding must not be null");
-        } else {
+            throw new IllegalArgumentException("inputEncoding must not be null");
+        }
+        synchronized (inMutex) {
             this.inputEncoding = inputEncoding;
         }
     }
 
     /**
-     * Controls whether error output of exec is logged. This is only useful
-     * when output is being redirected and error output is desired in the
-     * Ant log
-     *
-     * @param logError if true the standard error is sent to the Ant log system
-     *        and not sent to output.
-     */
-    public synchronized void setLogError(boolean logError) {
-        this.logError = logError;
+     * Controls whether error output of exec is logged. This is only useful when
+     * output is being redirected and error output is desired in the Ant log
+     * 
+     * @param logError
+     *            if true the standard error is sent to the Ant log system and
+     *            not sent to output.
+     */
+    public void setLogError(boolean logError) {
+        synchronized (errMutex) {
+            this.logError = logError;
+        }
     }
 
     /**
      * This <code>Redirector</code>'s subordinate
      * <code>PropertyOutputStream</code>s will not set their respective
      * properties <code>while (appendProperties && append)</code>.
-     *
-     * @param appendProperties whether to append properties.
-     */
-    public synchronized void setAppendProperties(boolean appendProperties) {
-        this.appendProperties = appendProperties;
+     * 
+     * @param appendProperties
+     *            whether to append properties.
+     */
+    public void setAppendProperties(boolean appendProperties) {
+        synchronized (outMutex) {
+            this.appendProperties = appendProperties;
+        }
     }
 
     /**
      * Set the file to which standard error is to be redirected.
-     *
-     * @param error the file to which error is to be written
+     * 
+     * @param error
+     *            the file to which error is to be written
      */
     public void setError(File error) {
-        setError((error == null) ? null : new File[] {error});
+        setError((error == null) ? null : new File[] { error });
     }
 
     /**
      * Set the files to which standard error is to be redirected.
-     *
-     * @param error the file to which error is to be written
-     */
-    public synchronized void setError(File[] error) {
-        this.error = error;
+     * 
+     * @param error
+     *            the file to which error is to be written
+     */
+    public void setError(File[] error) {
+        synchronized (errMutex) {
+            if (error == null) {
+                this.error = null;
+            } else {
+                this.error = error.clone();
+            }
+        }
     }
 
     /**
-     * Property name whose value should be set to the output of
-     * the process.
-     *
-     * @param outputProperty the name of the property to be set with the
-     *        task's output.
+     * Property name whose value should be set to the output of the process.
+     * 
+     * @param outputProperty
+     *            the name of the property to be set with the task's output.
      */
-    public synchronized void setOutputProperty(String outputProperty) {
+    public void setOutputProperty(String outputProperty) {
         if (outputProperty == null
-         || !(outputProperty.equals(this.outputProperty))) {
-            this.outputProperty = outputProperty;
-            baos = null;
+                || !(outputProperty.equals(this.outputProperty))) {
+            synchronized (outMutex) {
+                this.outputProperty = outputProperty;
+                baos = null;
+            }
         }
     }
 
     /**
      * Whether output should be appended to or overwrite an existing file.
      * Defaults to false.
-     *
-     * @param append if true output and error streams are appended to their
-     *        respective files, if specified.
+     * 
+     * @param append
+     *            if true output and error streams are appended to their
+     *            respective files, if specified.
      */
-    public synchronized void setAppend(boolean append) {
-        this.append = append;
+    public void setAppend(boolean append) {
+        synchronized (outMutex) {
+            appendOut = append;
+        }
+        synchronized (errMutex) {
+            appendErr = append;
+        }
     }
 
     /**
-     * If true, (error and non-error) output will be "teed", redirected
-     * as specified while being sent to Ant's logging mechanism as if no
-     * redirection had taken place.  Defaults to false.
-     * @param alwaysLog <code>boolean</code>
+     * If true, (error and non-error) output will be "teed", redirected as
+     * specified while being sent to Ant's logging mechanism as if no
+     * redirection had taken place. Defaults to false.
+     * 
+     * @param alwaysLog
+     *            <code>boolean</code>
      * @since Ant 1.6.3
      */
-    public synchronized void setAlwaysLog(boolean alwaysLog) {
-        this.alwaysLog = alwaysLog;
+    public void setAlwaysLog(boolean alwaysLog) {
+        synchronized (outMutex) {
+            alwaysLogOut = alwaysLog;
+        }
+        synchronized (errMutex) {
+            alwaysLogErr = alwaysLog;
+        }
     }
 
     /**
      * Whether output and error files should be created even when empty.
      * Defaults to true.
-     * @param createEmptyFiles <code>boolean</code>.
+     * 
+     * @param createEmptyFiles
+     *            <code>boolean</code>.
      */
-    public synchronized void setCreateEmptyFiles(boolean createEmptyFiles) {
-        this.createEmptyFiles = createEmptyFiles;
+    public void setCreateEmptyFiles(boolean createEmptyFiles) {
+        synchronized (outMutex) {
+            createEmptyFilesOut = createEmptyFiles;
+        }
+        synchronized (outMutex) {
+            createEmptyFilesErr = createEmptyFiles;
+        }
     }
 
     /**
-     * Property name whose value should be set to the error of
-     * the process.
-     *
-     * @param errorProperty the name of the property to be set
-     *        with the error output.
+     * Property name whose value should be set to the error of the process.
+     * 
+     * @param errorProperty
+     *            the name of the property to be set with the error output.
      */
-    public synchronized void setErrorProperty(String errorProperty) {
-        if (errorProperty == null
-         || !(errorProperty.equals(this.errorProperty))) {
-            this.errorProperty = errorProperty;
-            errorBaos = null;
+    public void setErrorProperty(String errorProperty) {
+        synchronized (errMutex) {
+            if (errorProperty == null
+                    || !(errorProperty.equals(this.errorProperty))) {
+                this.errorProperty = errorProperty;
+                errorBaos = null;
+            }
         }
     }
 
     /**
      * Set the input <code>FilterChain</code>s.
-     *
-     * @param inputFilterChains <code>Vector</code> containing <code>FilterChain</code>.
-     */
-    public synchronized void setInputFilterChains(Vector inputFilterChains) {
-        this.inputFilterChains = inputFilterChains;
+     * 
+     * @param inputFilterChains
+     *            <code>Vector</code> containing <code>FilterChain</code>.
+     */
+    public void setInputFilterChains(Vector inputFilterChains) {
+        synchronized (inMutex) {
+            this.inputFilterChains = inputFilterChains;
+        }
     }
 
     /**
      * Set the output <code>FilterChain</code>s.
-     *
-     * @param outputFilterChains <code>Vector</code> containing <code>FilterChain</code>.
-     */
-    public synchronized void setOutputFilterChains(Vector outputFilterChains) {
-        this.outputFilterChains = outputFilterChains;
+     * 
+     * @param outputFilterChains
+     *            <code>Vector</code> containing <code>FilterChain</code>.
+     */
+    public void setOutputFilterChains(Vector outputFilterChains) {
+        synchronized (outMutex) {
+            this.outputFilterChains = outputFilterChains;
+        }
     }
 
     /**
      * Set the error <code>FilterChain</code>s.
-     *
-     * @param errorFilterChains <code>Vector</code> containing <code>FilterChain</code>.
-     */
-    public synchronized void setErrorFilterChains(Vector errorFilterChains) {
-        this.errorFilterChains = errorFilterChains;
+     * 
+     * @param errorFilterChains
+     *            <code>Vector</code> containing <code>FilterChain</code>.
+     */
+    public void setErrorFilterChains(Vector errorFilterChains) {
+        synchronized (errMutex) {
+            this.errorFilterChains = errorFilterChains;
+        }
     }
 
     /**
      * Set a property from a ByteArrayOutputStream
-     *
-     * @param baos contains the property value.
-     * @param propertyName the property name.
-     *
-     * @exception IOException if the value cannot be read form the stream.
+     * 
+     * @param baos
+     *            contains the property value.
+     * @param propertyName
+     *            the property name.
+     * 
+     * @exception IOException
+     *                if the value cannot be read form the stream.
      */
     private void setPropertyFromBAOS(ByteArrayOutputStream baos,
-                                     String propertyName) throws IOException {
+            String propertyName) throws IOException {
 
-        BufferedReader in
-            = new BufferedReader(new StringReader(Execute.toString(baos)));
+        BufferedReader in = new BufferedReader(new StringReader(Execute
+                .toString(baos)));
         String line = null;
         StringBuffer val = new StringBuffer();
         while ((line = in.readLine()) != null) {
@@ -455,121 +549,138 @@
     }
 
     /**
-     * Create the input, error and output streams based on the
-     * configuration options.
+     * Create the input, error and output streams based on the configuration
+     * options.
      */
-    public synchronized void createStreams() {
-        outStreams();
-        errorStreams();
-        if (alwaysLog || outputStream == null) {
-            OutputStream outputLog
-                = new LogOutputStream(managingTask, Project.MSG_INFO);
-            outputStream = (outputStream == null)
-                ? outputLog : new TeeOutputStream(outputLog, outputStream);
-        }
-        if (alwaysLog || errorStream == null) {
-            OutputStream errorLog
-                = new LogOutputStream(managingTask, Project.MSG_WARN);
-            errorStream = (errorStream == null)
-                ? errorLog : new TeeOutputStream(errorLog, errorStream);
-        }
-        if ((outputFilterChains != null && outputFilterChains.size() > 0)
-            || !(outputEncoding.equalsIgnoreCase(inputEncoding))) {
-            try {
-                LeadPipeInputStream snk = new LeadPipeInputStream();
-                snk.setManagingComponent(managingTask);
-
-                InputStream outPumpIn = snk;
+    public void createStreams() {
 
-                Reader reader = new InputStreamReader(outPumpIn, inputEncoding);
+        synchronized (outMutex) {
+            outStreams();
+            if (alwaysLogOut || outputStream == null) {
+                OutputStream outputLog = new LogOutputStream(managingTask,
+                        Project.MSG_INFO);
+                outputStream = (outputStream == null) ? outputLog
+                        : new TeeOutputStream(outputLog, outputStream);
+            }
+
+            if ((outputFilterChains != null && outputFilterChains.size() > 0)
+                    || !(outputEncoding.equalsIgnoreCase(inputEncoding))) {
+                try {
+                    LeadPipeInputStream snk = new LeadPipeInputStream();
+                    snk.setManagingComponent(managingTask);
+
+                    InputStream outPumpIn = snk;
+
+                    Reader reader = new InputStreamReader(outPumpIn,
+                            inputEncoding);
+
+                    if (outputFilterChains != null
+                            && outputFilterChains.size() > 0) {
+                        ChainReaderHelper helper = new ChainReaderHelper();
+                        helper.setProject(managingTask.getProject());
+                        helper.setPrimaryReader(reader);
+                        helper.setFilterChains(outputFilterChains);
+                        reader = helper.getAssembledReader();
+                    }
+                    outPumpIn = new ReaderInputStream(reader, outputEncoding);
 
-                if (outputFilterChains != null && outputFilterChains.size() > 0) {
-                    ChainReaderHelper helper = new ChainReaderHelper();
-                    helper.setProject(managingTask.getProject());
-                    helper.setPrimaryReader(reader);
-                    helper.setFilterChains(outputFilterChains);
-                    reader = helper.getAssembledReader();
+                    Thread t = new Thread(threadGroup, new StreamPumper(
+                            outPumpIn, outputStream, true), "output pumper");
+                    t.setPriority(Thread.MAX_PRIORITY);
+                    outputStream = new PipedOutputStream(snk);
+                    t.start();
+                } catch (IOException eyeOhEx) {
+                    throw new BuildException("error setting up output stream",
+                            eyeOhEx);
                 }
-                outPumpIn = new ReaderInputStream(reader, outputEncoding);
-
-                Thread t = new Thread(threadGroup, new StreamPumper(
-                    outPumpIn, outputStream, true), "output pumper");
-                t.setPriority(Thread.MAX_PRIORITY);
-                outputStream = new PipedOutputStream(snk);
-                t.start();
-            } catch (IOException eyeOhEx) {
-                throw new BuildException(
-                    "error setting up output stream", eyeOhEx);
             }
         }
 
-        if ((errorFilterChains != null && errorFilterChains.size() > 0)
-            || !(errorEncoding.equalsIgnoreCase(inputEncoding))) {
-            try {
-                LeadPipeInputStream snk = new LeadPipeInputStream();
-                snk.setManagingComponent(managingTask);
-
-                InputStream errPumpIn = snk;
-
-                Reader reader = new InputStreamReader(errPumpIn, inputEncoding);
+        synchronized (errMutex) {
+            errorStreams();
+            if (alwaysLogErr || errorStream == null) {
+                OutputStream errorLog = new LogOutputStream(managingTask,
+                        Project.MSG_WARN);
+                errorStream = (errorStream == null) ? errorLog
+                        : new TeeOutputStream(errorLog, errorStream);
+            }
+
+            if ((errorFilterChains != null && errorFilterChains.size() > 0)
+                    || !(errorEncoding.equalsIgnoreCase(inputEncoding))) {
+                try {
+                    LeadPipeInputStream snk = new LeadPipeInputStream();
+                    snk.setManagingComponent(managingTask);
+
+                    InputStream errPumpIn = snk;
+
+                    Reader reader = new InputStreamReader(errPumpIn,
+                            inputEncoding);
+
+                    if (errorFilterChains != null
+                            && errorFilterChains.size() > 0) {
+                        ChainReaderHelper helper = new ChainReaderHelper();
+                        helper.setProject(managingTask.getProject());
+                        helper.setPrimaryReader(reader);
+                        helper.setFilterChains(errorFilterChains);
+                        reader = helper.getAssembledReader();
+                    }
+                    errPumpIn = new ReaderInputStream(reader, errorEncoding);
 
-                if (errorFilterChains != null && errorFilterChains.size() > 0) {
-                    ChainReaderHelper helper = new ChainReaderHelper();
-                    helper.setProject(managingTask.getProject());
-                    helper.setPrimaryReader(reader);
-                    helper.setFilterChains(errorFilterChains);
-                    reader = helper.getAssembledReader();
+                    Thread t = new Thread(threadGroup, new StreamPumper(
+                            errPumpIn, errorStream, true), "error pumper");
+                    t.setPriority(Thread.MAX_PRIORITY);
+                    errorStream = new PipedOutputStream(snk);
+                    t.start();
+                } catch (IOException eyeOhEx) {
+                    throw new BuildException("error setting up error stream",
+                            eyeOhEx);
                 }
-                errPumpIn = new ReaderInputStream(reader, errorEncoding);
-
-                Thread t = new Thread(threadGroup, new StreamPumper(
-                    errPumpIn, errorStream, true), "error pumper");
-                t.setPriority(Thread.MAX_PRIORITY);
-                errorStream = new PipedOutputStream(snk);
-                t.start();
-            } catch (IOException eyeOhEx) {
-                throw new BuildException(
-                    "error setting up error stream", eyeOhEx);
             }
         }
 
-        // if input files are specified, inputString and inputStream are ignored;
-        // classes that work with redirector attributes can enforce
-        // whatever warnings are needed
-        if (input != null && input.length > 0) {
-            managingTask.log("Redirecting input from file"
-                + ((input.length == 1) ? "" : "s"), Project.MSG_VERBOSE);
-            try {
-                inputStream = new ConcatFileInputStream(input);
-            } catch (IOException eyeOhEx) {
-                throw new BuildException(eyeOhEx);
-            }
-            ((ConcatFileInputStream) inputStream).setManagingComponent(managingTask);
-        } else if (inputString != null) {
-            StringBuffer buf = new StringBuffer("Using input ");
-            if (logInputString) {
-                buf.append('"').append(inputString).append('"');
-            } else {
-                buf.append("string");
+        synchronized (inMutex) {
+            // if input files are specified, inputString and inputStream are
+            // ignored;
+            // classes that work with redirector attributes can enforce
+            // whatever warnings are needed
+            if (input != null && input.length > 0) {
+                managingTask
+                        .log("Redirecting input from file"
+                                + ((input.length == 1) ? "" : "s"),
+                                Project.MSG_VERBOSE);
+                try {
+                    inputStream = new ConcatFileInputStream(input);
+                } catch (IOException eyeOhEx) {
+                    throw new BuildException(eyeOhEx);
+                }
+                ((ConcatFileInputStream) inputStream)
+                        .setManagingComponent(managingTask);
+            } else if (inputString != null) {
+                StringBuffer buf = new StringBuffer("Using input ");
+                if (logInputString) {
+                    buf.append('"').append(inputString).append('"');
+                } else {
+                    buf.append("string");
+                }
+                managingTask.log(buf.toString(), Project.MSG_VERBOSE);
+                inputStream = new ByteArrayInputStream(inputString.getBytes());
             }
-            managingTask.log(buf.toString(), Project.MSG_VERBOSE);
-            inputStream = new ByteArrayInputStream(inputString.getBytes());
-        }
 
-        if (inputStream != null
-            && inputFilterChains != null && inputFilterChains.size() > 0) {
-            ChainReaderHelper helper = new ChainReaderHelper();
-            helper.setProject(managingTask.getProject());
-            try {
-                helper.setPrimaryReader(
-                    new InputStreamReader(inputStream, inputEncoding));
-            } catch (IOException eyeOhEx) {
-                throw new BuildException(
-                    "error setting up input stream", eyeOhEx);
+            if (inputStream != null && inputFilterChains != null
+                    && inputFilterChains.size() > 0) {
+                ChainReaderHelper helper = new ChainReaderHelper();
+                helper.setProject(managingTask.getProject());
+                try {
+                    helper.setPrimaryReader(new InputStreamReader(inputStream,
+                            inputEncoding));
+                } catch (IOException eyeOhEx) {
+                    throw new BuildException("error setting up input stream",
+                            eyeOhEx);
+                }
+                helper.setFilterChains(inputFilterChains);
+                inputStream = new ReaderInputStream(
+                        helper.getAssembledReader(), inputEncoding);
             }
-            helper.setFilterChains(inputFilterChains);
-            inputStream = new ReaderInputStream(
-                helper.getAssembledReader(), inputEncoding);
         }
     }
 
@@ -577,20 +688,21 @@
     private void outStreams() {
         if (out != null && out.length > 0) {
             String logHead = new StringBuffer("Output ").append(
-                ((append) ? "appended" : "redirected")).append(
-                " to ").toString();
-            outputStream = foldFiles(out, logHead, Project.MSG_VERBOSE);
+                    ((appendOut) ? "appended" : "redirected")).append(" to ")
+                    .toString();
+            outputStream = foldFiles(out, logHead, Project.MSG_VERBOSE,
+                    appendOut, createEmptyFilesOut);
         }
         if (outputProperty != null) {
             if (baos == null) {
                 baos = new PropertyOutputStream(outputProperty);
                 managingTask.log("Output redirected to property: "
-                    + outputProperty, Project.MSG_VERBOSE);
+                        + outputProperty, Project.MSG_VERBOSE);
             }
-            //shield it from being closed by a filtering StreamPumper
+            // shield it from being closed by a filtering StreamPumper
             OutputStream keepAliveOutput = new KeepAliveOutputStream(baos);
             outputStream = (outputStream == null) ? keepAliveOutput
-                : new TeeOutputStream(outputStream, keepAliveOutput);
+                    : new TeeOutputStream(outputStream, keepAliveOutput);
         } else {
             baos = null;
         }
@@ -599,31 +711,32 @@
     private void errorStreams() {
         if (error != null && error.length > 0) {
             String logHead = new StringBuffer("Error ").append(
-                ((append) ? "appended" : "redirected")).append(
-                " to ").toString();
-            errorStream = foldFiles(error, logHead, Project.MSG_VERBOSE);
+                    ((appendErr) ? "appended" : "redirected")).append(" to ")
+                    .toString();
+            errorStream = foldFiles(error, logHead, Project.MSG_VERBOSE,
+                    appendErr, createEmptyFilesErr);
         } else if (!(logError || outputStream == null)) {
             long funnelTimeout = 0L;
-            OutputStreamFunneler funneler
-                = new OutputStreamFunneler(outputStream, funnelTimeout);
+            OutputStreamFunneler funneler = new OutputStreamFunneler(
+                    outputStream, funnelTimeout);
             try {
                 outputStream = funneler.getFunnelInstance();
                 errorStream = funneler.getFunnelInstance();
             } catch (IOException eyeOhEx) {
                 throw new BuildException(
-                    "error splitting output/error streams", eyeOhEx);
+                        "error splitting output/error streams", eyeOhEx);
             }
         }
         if (errorProperty != null) {
             if (errorBaos == null) {
                 errorBaos = new PropertyOutputStream(errorProperty);
-                managingTask.log("Error redirected to property: " + errorProperty,
-                    Project.MSG_VERBOSE);
+                managingTask.log("Error redirected to property: "
+                        + errorProperty, Project.MSG_VERBOSE);
             }
-            //shield it from being closed by a filtering StreamPumper
+            // shield it from being closed by a filtering StreamPumper
             OutputStream keepAliveError = new KeepAliveOutputStream(errorBaos);
             errorStream = (error == null || error.length == 0) ? keepAliveError
-                : new TeeOutputStream(errorStream, keepAliveError);
+                    : new TeeOutputStream(errorStream, keepAliveError);
         } else {
             errorBaos = null;
         }
@@ -631,198 +744,245 @@
 
     /**
      * Create the StreamHandler to use with our Execute instance.
-     *
-     * @return the execute stream handler to manage the input, output and
-     * error streams.
-     *
-     * @throws BuildException if the execute stream handler cannot be created.
+     * 
+     * @return the execute stream handler to manage the input, output and error
+     *         streams.
+     * 
+     * @throws BuildException
+     *             if the execute stream handler cannot be created.
      */
-    public synchronized ExecuteStreamHandler createHandler()
-        throws BuildException {
+    public ExecuteStreamHandler createHandler() throws BuildException {
         createStreams();
-        return new PumpStreamHandler(outputStream, errorStream, inputStream);
+        return new PumpStreamHandler(getOutputStream(), getErrorStream(),
+                getInputStream());
+
     }
 
     /**
      * Pass output sent to System.out to specified output.
-     *
-     * @param output the data to be output
-     */
-    protected synchronized void handleOutput(String output) {
-        if (outPrintStream == null) {
-            outPrintStream = new PrintStream(outputStream);
+     * 
+     * @param output
+     *            the data to be output
+     */
+    protected void handleOutput(String output) {
+        synchronized (outMutex) {
+            if (outPrintStream == null) {
+                outPrintStream = new PrintStream(outputStream);
+            }
+            outPrintStream.print(output);
         }
-        outPrintStream.print(output);
     }
 
     /**
      * Handle an input request
-     *
-     * @param buffer the buffer into which data is to be read.
-     * @param offset the offset into the buffer at which data is stored.
-     * @param length the amount of data to read
-     *
+     * 
+     * @param buffer
+     *            the buffer into which data is to be read.
+     * @param offset
+     *            the offset into the buffer at which data is stored.
+     * @param length
+     *            the amount of data to read
+     * 
      * @return the number of bytes read
-     *
-     * @exception IOException if the data cannot be read
-     */
-    protected synchronized int handleInput(byte[] buffer, int offset,
-                                           int length) throws IOException {
-        if (inputStream == null) {
-            return managingTask.getProject().defaultInput(buffer, offset,
-                                                          length);
-        } else {
+     * 
+     * @exception IOException
+     *                if the data cannot be read
+     */
+    protected int handleInput(byte[] buffer, int offset, int length)
+            throws IOException {
+        synchronized (inMutex) {
+            if (inputStream == null) {
+                return managingTask.getProject().defaultInput(buffer, offset,
+                        length);
+            }
             return inputStream.read(buffer, offset, length);
+
         }
     }
 
     /**
      * Process data due to a flush operation.
-     *
-     * @param output the data being flushed.
-     */
-    protected synchronized void handleFlush(String output) {
-        if (outPrintStream == null) {
-            outPrintStream = new PrintStream(outputStream);
+     * 
+     * @param output
+     *            the data being flushed.
+     */
+    protected void handleFlush(String output) {
+        synchronized (outMutex) {
+            if (outPrintStream == null) {
+                outPrintStream = new PrintStream(outputStream);
+            }
+            outPrintStream.print(output);
+            outPrintStream.flush();
         }
-        outPrintStream.print(output);
-        outPrintStream.flush();
     }
 
     /**
      * Process error output
-     *
-     * @param output the error output data.
-     */
-    protected synchronized void handleErrorOutput(String output) {
-        if (errorPrintStream == null) {
-            errorPrintStream = new PrintStream(errorStream);
+     * 
+     * @param output
+     *            the error output data.
+     */
+    protected void handleErrorOutput(String output) {
+        synchronized (errMutex) {
+            if (errorPrintStream == null) {
+                errorPrintStream = new PrintStream(errorStream);
+            }
+            errorPrintStream.print(output);
         }
-        errorPrintStream.print(output);
     }
 
     /**
      * Handle a flush operation on the error stream
-     *
-     * @param output the error information being flushed.
-     */
-    protected synchronized void handleErrorFlush(String output) {
-        if (errorPrintStream == null) {
-            errorPrintStream = new PrintStream(errorStream);
+     * 
+     * @param output
+     *            the error information being flushed.
+     */
+    protected void handleErrorFlush(String output) {
+        synchronized (errMutex) {
+            if (errorPrintStream == null) {
+                errorPrintStream = new PrintStream(errorStream);
+            }
+            errorPrintStream.print(output);
         }
-        errorPrintStream.print(output);
     }
 
     /**
      * Get the output stream for the redirector
-     *
-     * @return the redirector's output stream or null if no output
-     *         has been configured
-     */
-    public synchronized OutputStream getOutputStream() {
-        return outputStream;
+     * 
+     * @return the redirector's output stream or null if no output has been
+     *         configured
+     */
+    public OutputStream getOutputStream() {
+        synchronized (outMutex) {
+            return outputStream;
+        }
     }
 
     /**
      * Get the error stream for the redirector
-     *
-     * @return the redirector's error stream or null if no output
-     *         has been configured
-     */
-    public synchronized OutputStream getErrorStream() {
-        return errorStream;
+     * 
+     * @return the redirector's error stream or null if no output has been
+     *         configured
+     */
+    public OutputStream getErrorStream() {
+        synchronized (errMutex) {
+            return errorStream;
+        }
     }
 
     /**
      * Get the input stream for the redirector
-     *
-     * @return the redirector's input stream or null if no output
-     *         has been configured
-     */
-    public synchronized InputStream getInputStream() {
-        return inputStream;
+     * 
+     * @return the redirector's input stream or null if no output has been
+     *         configured
+     */
+    public InputStream getInputStream() {
+        synchronized (inMutex) {
+            return inputStream;
+        }
     }
 
     /**
      * Complete redirection.
-     *
-     * This operation will close any streams and create any specified
-     * property values.
-     *
-     * @throws IOException if the output properties cannot be read from their
-     * output streams.
+     * 
+     * This operation will close any streams and create any specified property
+     * values.
+     * 
+     * @throws IOException
+     *             if the output properties cannot be read from their output
+     *             streams.
      */
-    public synchronized void complete() throws IOException {
+    public void complete() throws IOException {
         System.out.flush();
         System.err.flush();
 
-        if (inputStream != null) {
-            inputStream.close();
+        synchronized (inMutex) {
+            if (inputStream != null) {
+                inputStream.close();
+            }
         }
 
-        outputStream.flush();
-        outputStream.close();
-
-        errorStream.flush();
-        errorStream.close();
-
-        //wait for the StreamPumpers to finish
-        while (threadGroup.activeCount() > 0) {
-            try {
-                managingTask.log("waiting for " + threadGroup.activeCount()
-                    + " Threads:", Project.MSG_DEBUG);
-                Thread[] thread = new Thread[threadGroup.activeCount()];
-                threadGroup.enumerate(thread);
-                for (int i = 0; i < thread.length && thread[i] != null; i++) {
-                    try {
-                        managingTask.log(thread[i].toString(), Project.MSG_DEBUG);
-                    } catch (NullPointerException enPeaEx) {
-                        // Ignore exception
+        synchronized (outMutex) {
+            outputStream.flush();
+            outputStream.close();
+        }
+
+        synchronized (errMutex) {
+            errorStream.flush();
+            errorStream.close();
+        }
+
+        // wait for the StreamPumpers to finish
+        synchronized (this) {
+            while (threadGroup.activeCount() > 0) {
+                try {
+                    managingTask.log("waiting for " + threadGroup.activeCount()
+                            + " Threads:", Project.MSG_DEBUG);
+                    Thread[] thread = new Thread[threadGroup.activeCount()];
+                    threadGroup.enumerate(thread);
+                    for (int i = 0; i < thread.length && thread[i] != null; i++) {
+                        try {
+                            managingTask.log(thread[i].toString(),
+                                    Project.MSG_DEBUG);
+                        } catch (NullPointerException enPeaEx) {
+                            // Ignore exception
+                        }
+                    }
+                    wait(STREAMPUMPER_WAIT_INTERVAL);
+                } catch (InterruptedException eyeEx) {
+                    Thread[] thread = new Thread[threadGroup.activeCount()];
+                    threadGroup.enumerate(thread);
+                    for (int i = 0; i < thread.length && thread[i] != null; i++) {
+                        thread[i].interrupt();
                     }
-                }
-                wait(STREAMPUMPER_WAIT_INTERVAL);
-            } catch (InterruptedException eyeEx) {
-                Thread[] thread = new Thread[threadGroup.activeCount()];
-                threadGroup.enumerate(thread);
-                for (int i = 0; i < thread.length && thread[i] != null; i++) {
-                    thread[i].interrupt();
                 }
             }
         }
 
         setProperties();
 
-        inputStream = null;
-        outputStream = null;
-        errorStream = null;
-        outPrintStream = null;
-        errorPrintStream = null;
-   }
+        synchronized (inMutex) {
+            inputStream = null;
+        }
+        synchronized (outMutex) {
+            outputStream = null;
+            outPrintStream = null;
+        }
+        synchronized (errMutex) {
+            errorStream = null;
+            errorPrintStream = null;
+        }
+    }
 
     /**
-     * Notify the <code>Redirector</code> that it is now okay
-     * to set any output and/or error properties.
+     * Notify the <code>Redirector</code> that it is now okay to set any output
+     * and/or error properties.
      */
-    public synchronized void setProperties() {
-        if (baos != null) {
-            try {
-                baos.close();
-            } catch (IOException eyeOhEx) {
-                // Ignore exception
+    public void setProperties() {
+        synchronized (outMutex) {
+            if (baos != null) {
+                try {
+                    baos.close();
+                } catch (IOException eyeOhEx) {
+                    // Ignore exception
+                }
             }
         }
-        if (errorBaos != null) {
-            try {
-                errorBaos.close();
-            } catch (IOException eyeOhEx) {
-                // Ignore exception
+        synchronized (errMutex) {
+            if (errorBaos != null) {
+                try {
+                    errorBaos.close();
+                } catch (IOException eyeOhEx) {
+                    // Ignore exception
+                }
             }
         }
     }
 
-    private OutputStream foldFiles(File[] file, String logHead, int loglevel) {
-        OutputStream result
-            = new LazyFileOutputStream(file[0], append, createEmptyFiles);
+    private OutputStream foldFiles(File[] file, String logHead, int loglevel,
+            boolean append, boolean createEmptyFiles) {
+        OutputStream result = new LazyFileOutputStream(file[0], append,
+                createEmptyFiles);
 
         managingTask.log(logHead + file[0], loglevel);
         char[] c = new char[logHead.length()];
@@ -831,7 +991,7 @@
 
         for (int i = 1; i < file.length; i++) {
             outputStream = new TeeOutputStream(outputStream,
-                new LazyFileOutputStream(file[i], append, createEmptyFiles));
+                    new LazyFileOutputStream(file[i], append, createEmptyFiles));
             managingTask.log(indent + file[i], loglevel);
         }
         return result;