Bug 47642

Summary: PumpStreamHandler can interleave I/O in incorrect order
Product: Ant Reporter: Jesse Glick <jglick>
Component: Core tasksAssignee: Ant Notifications List <notifications>
Status: NEW ---    
Severity: normal    
Priority: P2    
Version: 1.7.1   
Target Milestone: ---   
Hardware: PC   
OS: Linux   

Description Jesse Glick 2009-08-04 13:48:30 UTC
Consider


<project default="run">
    <available property="compiled" file="Test.class"/>
    <target name="compile" unless="compiled">
        <echo file="Test.java">
public class Test {
    public static void main(String[] args) {
        System.out.println("out");
        System.out.flush();
        System.err.println("err");
    }
}
        </echo>
        <javac srcdir="." destdir="." includeantruntime="false" source="1.5" includes="Test.java"/>
    </target>
    <target name="run" depends="compile">
        <java fork="true" classname="Test" classpath="."/>
    </target>
</project>


You would expect this program to always print "out" followed by "err", and when run without Ant it does. When run using Ant, occasionally it prints "err" followed by "out". This can be made frequent enough to observe by introducing some artificial latency:


Index: src/main/org/apache/tools/ant/taskdefs/StreamPumper.java
===================================================================
--- src/main/org/apache/tools/ant/taskdefs/StreamPumper.java	(revision 799653)
+++ src/main/org/apache/tools/ant/taskdefs/StreamPumper.java	(working copy)
@@ -20,6 +20,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Random;
 import org.apache.tools.ant.util.FileUtils;
 
 /**
@@ -115,6 +116,11 @@
         synchronized (this) {
             started = true;
         }
+        if (new Random().nextBoolean()) {
+            try {
+                Thread.sleep(1000);
+            } catch (InterruptedException ex) {}
+        }
         finished = false;
         finish = false;
 

The trouble is that PumpStreamHandler spawns one copier thread per I/O stream, and there is no guarantee that these threads will not be preempted. "out\n" becomes available on process.inputStream just before "err\n" becomes available on process.errorStream, so only if the stdout StreamPumper is ready to run will it actually run before the stderr StreamPumper. In other words, there is a race condition.

As for a possible fix, java.nio.channels.Selector should be able to poll all the process I/O streams deterministically. You would still need a separate copier thread, but at least select() should return 1 after out.flush() was called, and selectedKeys() should have stdout only, so the next call to select() would happen only after stdout was processed. At least I think so.


Original report: http://www.netbeans.org/nonav/issues/show_bug.cgi?id=145980