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 Bryzak | |||
Adam Sotona | |||
Aleksandr Ishutin | |||
Alexey Panchenko | |||
Alexey Solofnenko | |||
@@ -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 <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: | |||
----------- | |||
@@ -38,6 +38,10 @@ | |||
<first>Adam</first> | |||
<last>Bryzak</last> | |||
</name> | |||
<name> | |||
<first>Adam</first> | |||
<last>Sotona</last> | |||
</name> | |||
<name> | |||
<first>Aleksandr</first> | |||
<last>Ishutin</last> | |||
@@ -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 <code>OutputStream</code>. | |||
@@ -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; | |||
} | |||
} | |||
} |
@@ -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. | |||
* | |||
* <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.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); | |||
} | |||
} | |||
} | |||
} | |||
} |