JDK-4016179 : java.io.PipedOutputStream.write methods hang if reader thread dies
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 1.0.2
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: solaris_2.5
  • CPU: sparc
  • Submitted: 1996-11-22
  • Updated: 1999-01-15
  • Resolved: 1999-01-15
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
1.2.0 1.2beta4Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Description
This bug was found by St.Petersburg Java SQE team (by Stanislav Avzan).


As follows from comments on bugs 1266819 and 1267045, the specs should 
be read as follows: 
"22.17.4 public void write(int b) throws IOException 

If a thread was reading data bytes from the connected piped input stream,
but the thread is no longer alive <and write buffer is full>, then an 
IOException is thrown."

The attempt of such approach is seen from source code. In fact, however,
write call may wait infinitely for the reader that is already dead.
Here is the test demonstraiting the bug:
----- test16.java ---------------------------------------
import java.io.*;

public class test16 {

  public static void main( String[] argv ) {
    PipedOutputStream os = new PipedOutputStream(); 
    PipedInputStream is= new PipedInputStream();
    LazyReader lr;
    int rl;
    try {
      is.connect(os); 
     } catch(Throwable e) {
       System.out.println("Test failed: unexpected <"+e+"> thrown");
       System.exit(1); 
     } 
    byte[] data = new byte[1000];
    lr = new LazyReader(is,"Test",1);// create reader thread with minimal delay
    try {
      os.write(data); //step write 1000 bytes of data
      lr.start();//step start reading bytes of data
      try {
        Thread.sleep(1000); //step sleep for 1 second
      } catch (InterruptedException e1) {}
      if(!(
        is.available() == 0 //step test reader is active
      ))
       { System.out.println("Test failed: no preliminary readings performed" );
         System.exit(1);
       }
    }
    catch(Throwable e) { 
       System.out.println("Test failed: unexpected <"+e+"> thrown");
       System.exit(1);
    }
  //here starts the core of the test
    try {
      lr.stop(); //step stop readerer thread
      System.out.println("Is producer alive? - "+lr.isAlive());
      try {
        Thread.sleep(1000); //step sleep for 1 second
      } catch (InterruptedException e1) {}
 // next line will execute forever 
      for(int i=0;i<1025;i++) os.write(i); //step try to write into dead thread
        //remembering that buffer size is 1024
      System.out.println("Test failed: no exceptions thrown");
    }
    catch(IOException e) // test IOException is thrown
    { System.out.println("Test passed: IOException thrown");   
    } catch(Throwable e) {
      System.out.println("Test failed: unexpected <"+e+"> thrown"); 
    } 
  }
}
------- LazyReader.java ---------------------------------
import java.io.*;

public class LazyReader extends Thread {
  private PipedInputStream snk;
  private String testid;
  private int delay;

  public LazyReader(PipedInputStream snk, String testid, int delay) {
    this.snk = snk;
    this.testid = testid;
    this.delay = delay;
  }
  
  public void run() {
    try {
      while(true) {
        try {
          Thread.sleep(delay);
        } catch(InterruptedException e) {
        }
        while(snk.available() > 0) snk.read(); //read all written
      }
    } catch(IOException e1) {
        System.out.println(testid + " failed: PipedOutputStream write error");
        System.exit(1);
    }
  }      

}
----- The output of the test: -------------------------

$JAVA test16
Is producer alive? - false
^C
-------------------------------------------------------
The test has to be interrupted 
 
Here is the fix I'd like to recommend:
The reader and writer threads are represented in PipedInputStream
class as readSide and writeSide. 
writeSide is initialized by any write call, but readSide - only
if buffer is empty. In code fragment below:
---PipedInputStream.java--- 
1    public synchronized int read()  throws IOException {
2	int trials = 2;
3	while (in < 0) {
4	    readSide = Thread.currentThread();
5	    if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0))   
I recommend to move line 4 out of the loop, after line 2.

1    public synchronized int read()  throws IOException {
2	int trials = 2;
3	readSide = Thread.currentThread();
4	while (in < 0) {
5	    if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0))


Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: generic FIXED IN: 1.2beta4 INTEGRATED IN: 1.2beta4
14-06-2004

EVALUATION Yet another piped-stream inconsistency. Coding error. Now throws IOException if single reader thread dies. ###@###.### 1998-03-09
09-03-1998