git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@711860 13f79535-47bb-0310-9956-ffa450edef68master
| @@ -2,6 +2,7 @@ Amongst other, the following people contributed to ant: | |||||
| Adam Blinkinsop | Adam Blinkinsop | ||||
| Adam Bryzak | Adam Bryzak | ||||
| Adam Sotona | |||||
| Aleksandr Ishutin | Aleksandr Ishutin | ||||
| Alexey Panchenko | Alexey Panchenko | ||||
| Alexey Solofnenko | Alexey Solofnenko | ||||
| @@ -113,6 +113,14 @@ Changes that could break older environments: | |||||
| http://ant.apache.org/antlibs/dotnet/index.html | http://ant.apache.org/antlibs/dotnet/index.html | ||||
| instead. | instead. | ||||
| * the logic of closing streams connected to forked processes (read | |||||
| the input and output of <exec> 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: | Fixed bugs: | ||||
| ----------- | ----------- | ||||
| @@ -38,6 +38,10 @@ | |||||
| <first>Adam</first> | <first>Adam</first> | ||||
| <last>Bryzak</last> | <last>Bryzak</last> | ||||
| </name> | </name> | ||||
| <name> | |||||
| <first>Adam</first> | |||||
| <last>Sotona</last> | |||||
| </name> | |||||
| <name> | <name> | ||||
| <first>Aleksandr</first> | <first>Aleksandr</first> | ||||
| <last>Ishutin</last> | <last>Ishutin</last> | ||||
| @@ -21,6 +21,7 @@ package org.apache.tools.ant.taskdefs; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.io.InputStream; | import java.io.InputStream; | ||||
| import java.io.OutputStream; | import java.io.OutputStream; | ||||
| import org.apache.tools.ant.taskdefs.condition.Os; | |||||
| /** | /** | ||||
| * Copies standard output and error of subprocesses to standard output and | * Copies standard output and error of subprocesses to standard output and | ||||
| @@ -129,16 +130,8 @@ public class PumpStreamHandler implements ExecuteStreamHandler { | |||||
| * Stop pumping the streams. | * Stop pumping the streams. | ||||
| */ | */ | ||||
| public void stop() { | public void stop() { | ||||
| try { | |||||
| outputThread.join(); | |||||
| } catch (InterruptedException e) { | |||||
| // ignore | |||||
| } | |||||
| try { | |||||
| errorThread.join(); | |||||
| } catch (InterruptedException e) { | |||||
| // ignore | |||||
| } | |||||
| finish(outputThread); | |||||
| finish(errorThread); | |||||
| if (inputPump != null) { | if (inputPump != null) { | ||||
| inputPump.stop(); | 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. | * Get the error stream. | ||||
| * @return <code>OutputStream</code>. | * @return <code>OutputStream</code>. | ||||
| @@ -207,12 +229,16 @@ public class PumpStreamHandler implements ExecuteStreamHandler { | |||||
| * @param is the input stream to copy from. | * @param is the input stream to copy from. | ||||
| * @param os the output stream to copy to. | * @param os the output stream to copy to. | ||||
| * @param closeWhenExhausted if true close the inputstream. | * @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, | protected Thread createPump(InputStream is, OutputStream os, | ||||
| boolean closeWhenExhausted) { | boolean closeWhenExhausted) { | ||||
| final Thread result | final Thread result | ||||
| = new Thread(new StreamPumper(is, os, closeWhenExhausted)); | |||||
| = new ThreadWithPumper(new StreamPumper(is, os, | |||||
| closeWhenExhausted, | |||||
| Os.isFamily("windows"))); | |||||
| result.setDaemon(true); | result.setDaemon(true); | ||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -224,9 +250,25 @@ public class PumpStreamHandler implements ExecuteStreamHandler { | |||||
| */ | */ | ||||
| /*protected*/ StreamPumper createInputPump(InputStream is, OutputStream os, | /*protected*/ StreamPumper createInputPump(InputStream is, OutputStream os, | ||||
| boolean closeWhenExhausted) { | boolean closeWhenExhausted) { | ||||
| StreamPumper pumper = new StreamPumper(is, os, closeWhenExhausted); | |||||
| StreamPumper pumper = new StreamPumper(is, os, closeWhenExhausted, | |||||
| false); | |||||
| pumper.setAutoflush(true); | pumper.setAutoflush(true); | ||||
| return pumper; | 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; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| @@ -20,6 +20,7 @@ package org.apache.tools.ant.taskdefs; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.io.InputStream; | import java.io.InputStream; | ||||
| import java.io.OutputStream; | import java.io.OutputStream; | ||||
| import org.apache.tools.ant.util.FileUtils; | |||||
| /** | /** | ||||
| * Copies all data from an input stream to an output stream. | * 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 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 finish; | ||||
| private volatile boolean finished; | private volatile boolean finished; | ||||
| private boolean closeWhenExhausted; | |||||
| private final boolean closeWhenExhausted; | |||||
| private boolean autoflush = false; | private boolean autoflush = false; | ||||
| private Exception exception = null; | private Exception exception = null; | ||||
| private int bufferSize = SMALL_BUFFER_SIZE; | private int bufferSize = SMALL_BUFFER_SIZE; | ||||
| private boolean started = false; | private boolean started = false; | ||||
| private final boolean useAvailable; | |||||
| /** | /** | ||||
| * Create a new StreamPumper. | * Create a new StreamPumper. | ||||
| @@ -49,9 +51,40 @@ public class StreamPumper implements Runnable { | |||||
| * the input is exhausted. | * the input is exhausted. | ||||
| */ | */ | ||||
| public StreamPumper(InputStream is, OutputStream os, boolean closeWhenExhausted) { | public StreamPumper(InputStream is, OutputStream os, boolean closeWhenExhausted) { | ||||
| this(is, os, closeWhenExhausted, false); | |||||
| } | |||||
| /** | |||||
| * Create a new StreamPumper. | |||||
| * | |||||
| * <p><b>Note:</b> 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.</p> | |||||
| * | |||||
| * @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.is = is; | ||||
| this.os = os; | this.os = os; | ||||
| this.closeWhenExhausted = closeWhenExhausted; | this.closeWhenExhausted = closeWhenExhausted; | ||||
| this.useAvailable = useAvailable; | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -90,8 +123,14 @@ public class StreamPumper implements Runnable { | |||||
| int length; | int length; | ||||
| try { | try { | ||||
| while (true) { | while (true) { | ||||
| waitForInput(is); | |||||
| if (finish || Thread.interrupted()) { | |||||
| break; | |||||
| } | |||||
| length = is.read(buf); | length = is.read(buf); | ||||
| if ((length <= 0) || finish) { | |||||
| if (length <= 0 || finish || Thread.interrupted()) { | |||||
| break; | break; | ||||
| } | } | ||||
| os.write(buf, 0, length); | os.write(buf, 0, length); | ||||
| @@ -100,17 +139,15 @@ public class StreamPumper implements Runnable { | |||||
| } | } | ||||
| } | } | ||||
| os.flush(); | os.flush(); | ||||
| } catch (InterruptedException ie) { | |||||
| // likely PumpStreamHandler trying to stop us | |||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| synchronized (this) { | synchronized (this) { | ||||
| exception = e; | exception = e; | ||||
| } | } | ||||
| } finally { | } finally { | ||||
| if (closeWhenExhausted) { | if (closeWhenExhausted) { | ||||
| try { | |||||
| os.close(); | |||||
| } catch (IOException e) { | |||||
| // ignore | |||||
| } | |||||
| FileUtils.close(os); | |||||
| } | } | ||||
| finished = true; | finished = true; | ||||
| synchronized (this) { | synchronized (this) { | ||||
| @@ -177,4 +214,22 @@ public class StreamPumper implements Runnable { | |||||
| finish = true; | finish = true; | ||||
| notifyAll(); | 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); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||