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); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | } |