JDK-4479751 : (spec) InputStream.read() implementations block when they should return immediately
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 1.3.1
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2001-07-13
  • Updated: 2017-05-16
  • Resolved: 2006-04-14
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.
JDK 6
6 b81Fixed
Related Reports
Relates :  
Description
Name: bsC130419			Date: 07/13/2001


happens in many JREs, including Apple's MRJ 2.2/Mac, and Sun's JRE 1.3/Linux

/* BufferedInputStream blocking problem

Problem:

   BufferedInputStream blocks until enough bytes are available to fill its
   internal buffer, even if fewer bytes have been requested.  In a client/
   server application, this can cause a deadlock: the client waits forever
   for the buffer to be filled, while the server waits forever for the
   client to finish processing the response and issue another request.

The output of this test program should look like this:

    $ java BisBugTest
    read 0x1020304 in 0 milliseconds.
    read 0x1020304 in 1004 milliseconds.
    read 0x1020304 in 1010 milliseconds.
    read 0x1020304 in 1010 milliseconds.
    read 0x1020304 in 1010 milliseconds.

Instead, it looks like this:

    $ java BisBugTest
    read 0x1020304 in 515964 milliseconds.
    read 0x1020304 in 0 milliseconds.
    read 0x1020304 in 0 milliseconds.
    read 0x1020304 in 0 milliseconds.
    read 0x1020304 in 0 milliseconds.

In this test run, the first read was delayed by 516 seconds (about 9 minutes).
*/

import java.io.*;

public class BisBugTest {

   public static void main (String args []) {
      try {
          // This creates a stream that sends 1,2,3,4; waits 1 second; repeats
          InputStream r = new RepeatStream (new byte [] {1, 2, 3, 4}, 1000);
          DataInputStream d = new DataInputStream (new BufferedInputStream
(r));
       // DataInputStream d = new DataInputStream (r); // this way it works OK
          while (true) {
             long start = System.currentTimeMillis ();
             int i = d.readInt ();
             long end = System.currentTimeMillis ();
             System.out.print ("read 0x" + Integer.toHexString (i));
             System.out.println (" in " + (end - start) + " milliseconds.");
          }
      } catch (Exception e) {
          e.printStackTrace ();
      }
   }

   // This input stream emulates a server that sends a series of bytes, waits a
   // while, sends the same series of bytes again, waits again, ad infinitum...
   public static class RepeatStream extends InputStream {

      private int sleepMillis;
      private byte contents [];
      private int index = 0;

      public RepeatStream (byte contents [], int sleepMillis) {
         this.contents = contents;
         this.sleepMillis = sleepMillis;
      }

      public int available () {
         return contents.length - index;
      }

      public int read ()
      throws IOException {
         if (index == contents.length) {
            sleep (sleepMillis);
            index = 0;
         }
         return contents [index++] & 0xff;
      }

      private static void sleep (int timeout)
      throws IOException {
         try {
            Thread.sleep (timeout);
         } catch (InterruptedException e) {
            throw new InterruptedIOException (e.getMessage ());
         }
      }
   }
}


(Review ID: 126530) 
======================================================================

Comments
EVALUATION The issue described in the description section of this bug has been fixed by 6192696. BufferedInputStream will no longer try to fill its internal buffer blindly, it will determine how much to fill based on the amount available from the underlying stream. In the case where there is no data available BufferedInputStream will block. Note: The testcase described in the description section needs to be changed as the RepeatStream.available() method is incorrect. It should reset the available count, so: public int available () { if (index == contents.length) index = 0; return contents.length - index; } There is however still the issue that InputStream(byte[],int,int) is blocking. This has been the case since InputStream was first created and some programs may rely on this behavior. The best solution here (seeing as the original bug has been fixed) is to document the behavior in the javadoc.
20-03-2006

SUGGESTED FIX ------- InputStream.java ------- *** /tmp/sccs.QmaGIt Thu Sep 1 12:57:57 2005 --- InputStream.java Thu Sep 1 12:47:04 2005 *************** *** 157,168 **** int i = 1; try { ! for (; i < len ; i++) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; } } catch (IOException ee) { } --- 157,170 ---- int i = 1; try { ! // read until buffer full, EOF, or no data available ! while ((i < len) && (available() > 0) { c = read(); if (c == -1) { break; } b[off + i] = (byte)c; + i++; } } catch (IOException ee) { }
01-09-2005

EVALUATION It turns out that the InputStream's read(byte[],int,int) was originally implemented to block until it fills the given buffer or EOF is reached. It does not return with a smaller number of bytes unless EOF is reached or an I/O error is encountered after reading the first byte. This issue appears to have existed forever and should only impact concrete InputStream implementations that do not override this method.
01-09-2005

EVALUATION Not a bug. The code provided blocks because its RepeatStream class does not override the InputStream.read(byte[], int, int) method. The definition of that method in the InputStream class knows nothing about available() (and indeed should not) so it blindly attempts to fill the entire array that is passed to it by BufferedInputStream, pausing every four bytes to sleep. In more realistic settings, e.g., using a DataInputStream to read from a TCP connection, this is not a problem because the underlying socket stream implements its own read(byte[], int, int) method that reads just those bytes that are immediately available. -- mr@eng 2001/8/3 I take that back -- there is a bug here. The BufferedInputStream code does invoke the available() method of the underlying stream, but it does not use the resulting value to limit the number of bytes that it later attempts to read from that stream. (See the JDC comments for a detailed explanation.) Fixing this should be straightforward. -- mr@eng 2001/8/7 Actually, it's not. The block occurs in the first call to read1(), before the invocation of available(). read1() calls fill() which calls read() on the underlying stream. We can play games to request the number of bytes based on what is available, but this solution does not always have the desired effect. We probably need to track the number of times that the stream is blocked. This is all definitely doable, but it is way too late in the release to fix this problem. -- iag@sfbay 2001-09-25
25-09-2001