diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 288171837..a8fcb017a 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -2,6 +2,7 @@ Amongst other, the following people contributed to ant: Adam Blinkinsop Adam Bryzak +Adam Sotona Aleksandr Ishutin Alexey Panchenko Alexey Solofnenko diff --git a/WHATSNEW b/WHATSNEW index 495066888..59600904a 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -113,6 +113,14 @@ Changes that could break older environments: http://ant.apache.org/antlibs/dotnet/index.html instead. + * the logic of closing streams connected to forked processes (read + the input and output of and friends) has been changed to + deal with cases where child processes of the forked processes live + longer than their parents and keep Ant from exiting. + It is unlikely but possible that the changed logic breaks stream + handling on certain Java VMs. + Bugzilla issue 5003. + Fixed bugs: ----------- diff --git a/contributors.xml b/contributors.xml index 6e654d11d..20535caad 100644 --- a/contributors.xml +++ b/contributors.xml @@ -38,6 +38,10 @@ Adam Bryzak + + Adam + Sotona + Aleksandr Ishutin diff --git a/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java b/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java index a1524b3a8..46576d475 100644 --- a/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java +++ b/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java @@ -21,6 +21,7 @@ package org.apache.tools.ant.taskdefs; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import org.apache.tools.ant.taskdefs.condition.Os; /** * Copies standard output and error of subprocesses to standard output and @@ -129,16 +130,8 @@ public class PumpStreamHandler implements ExecuteStreamHandler { * Stop pumping the streams. */ public void stop() { - try { - outputThread.join(); - } catch (InterruptedException e) { - // ignore - } - try { - errorThread.join(); - } catch (InterruptedException e) { - // ignore - } + finish(outputThread); + finish(errorThread); if (inputPump != null) { inputPump.stop(); @@ -156,6 +149,35 @@ public class PumpStreamHandler implements ExecuteStreamHandler { } } + private static final long JOIN_TIMEOUT = 500; + + /** + * Waits for a thread to finish while trying to make it finish + * quicker by stopping the pumper (if the thread is a {@link + * ThreadWithPumper ThreadWithPumper} instance) or interrupting + * the thread. + * + * @since Ant 1.8.0 + */ + protected final void finish(Thread t) { + try { + t.join(JOIN_TIMEOUT); + StreamPumper s = null; + if (t instanceof ThreadWithPumper) { + s = ((ThreadWithPumper) t).getPumper(); + } + if (s != null && !s.isFinished()) { + s.stop(); + } + while ((s == null || !s.isFinished()) && t.isAlive()) { + t.interrupt(); + t.join(JOIN_TIMEOUT); + } + } catch (InterruptedException e) { + // ignore + } + } + /** * Get the error stream. * @return OutputStream. @@ -207,12 +229,16 @@ public class PumpStreamHandler implements ExecuteStreamHandler { * @param is the input stream to copy from. * @param os the output stream to copy to. * @param closeWhenExhausted if true close the inputstream. - * @return a thread object that does the pumping. + * @return a thread object that does the pumping, subclasses + * should return an instance of {@link ThreadWithPumper + * ThreadWithPumper}. */ protected Thread createPump(InputStream is, OutputStream os, boolean closeWhenExhausted) { final Thread result - = new Thread(new StreamPumper(is, os, closeWhenExhausted)); + = new ThreadWithPumper(new StreamPumper(is, os, + closeWhenExhausted, + Os.isFamily("windows"))); result.setDaemon(true); return result; } @@ -224,9 +250,25 @@ public class PumpStreamHandler implements ExecuteStreamHandler { */ /*protected*/ StreamPumper createInputPump(InputStream is, OutputStream os, boolean closeWhenExhausted) { - StreamPumper pumper = new StreamPumper(is, os, closeWhenExhausted); + StreamPumper pumper = new StreamPumper(is, os, closeWhenExhausted, + false); pumper.setAutoflush(true); return pumper; } + /** + * Specialized subclass that allows access to the running StreamPumper. + * + * @since Ant 1.8.0 + */ + protected static class ThreadWithPumper extends Thread { + private final StreamPumper pumper; + public ThreadWithPumper(StreamPumper p) { + super(p); + pumper = p; + } + protected StreamPumper getPumper() { + return pumper; + } + } } diff --git a/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java b/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java index 6de3fc090..c4ad9914a 100644 --- a/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java +++ b/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java @@ -20,6 +20,7 @@ package org.apache.tools.ant.taskdefs; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import org.apache.tools.ant.util.FileUtils; /** * Copies all data from an input stream to an output stream. @@ -30,15 +31,16 @@ public class StreamPumper implements Runnable { private static final int SMALL_BUFFER_SIZE = 128; - private InputStream is; - private OutputStream os; + private final InputStream is; + private final OutputStream os; private volatile boolean finish; private volatile boolean finished; - private boolean closeWhenExhausted; + private final boolean closeWhenExhausted; private boolean autoflush = false; private Exception exception = null; private int bufferSize = SMALL_BUFFER_SIZE; private boolean started = false; + private final boolean useAvailable; /** * Create a new StreamPumper. @@ -49,9 +51,40 @@ public class StreamPumper implements Runnable { * the input is exhausted. */ public StreamPumper(InputStream is, OutputStream os, boolean closeWhenExhausted) { + this(is, os, closeWhenExhausted, false); + } + + + /** + * Create a new StreamPumper. + * + *

Note: If you set useAvailable to true, you must + * explicitly invoke {@link #stop stop} or interrupt the + * corresponding Thread when you are done or the run method will + * never finish on some JVMs (namely those where available returns + * 0 on a closed stream). Setting it to true may also impact + * performance negatively. This flag should only be set to true + * if you intend to stop the pumper before the input stream gets + * closed.

+ * + * @param is input stream to read data from + * @param os output stream to write data to. + * @param closeWhenExhausted if true, the output stream will be closed when + * the input is exhausted. + * @param useAvailable whether the pumper should use {@link + * java.io.InputStream#available available} to determine + * whether input is ready, thus trying to emulate + * non-blocking behavior. + * + * @since Ant 1.8.0 + */ + public StreamPumper(InputStream is, OutputStream os, + boolean closeWhenExhausted, + boolean useAvailable) { this.is = is; this.os = os; this.closeWhenExhausted = closeWhenExhausted; + this.useAvailable = useAvailable; } /** @@ -90,8 +123,14 @@ public class StreamPumper implements Runnable { int length; try { while (true) { + waitForInput(is); + + if (finish || Thread.interrupted()) { + break; + } + length = is.read(buf); - if ((length <= 0) || finish) { + if (length <= 0 || finish || Thread.interrupted()) { break; } os.write(buf, 0, length); @@ -100,17 +139,15 @@ public class StreamPumper implements Runnable { } } os.flush(); + } catch (InterruptedException ie) { + // likely PumpStreamHandler trying to stop us } catch (Exception e) { synchronized (this) { exception = e; } } finally { if (closeWhenExhausted) { - try { - os.close(); - } catch (IOException e) { - // ignore - } + FileUtils.close(os); } finished = true; synchronized (this) { @@ -177,4 +214,22 @@ public class StreamPumper implements Runnable { finish = true; notifyAll(); } + + private static final long POLL_INTERVAL = 100; + + private void waitForInput(InputStream is) + throws IOException, InterruptedException { + if (useAvailable) { + while (!finish && is.available() == 0) { + if (Thread.interrupted()) { + throw new InterruptedException(); + } + + synchronized (this) { + this.wait(POLL_INTERVAL); + } + } + } + } + }