JDK-6322678 : FileInputStream(FileDescriptor) throws IOException when reading a file if FD is invalid
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 1.1.4,1.3.0,5.0u4,5.0u11,6,6u20
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS:
    solaris,windows_nt,windows_2000,windows_xp,windows_2008 solaris,windows_nt,windows_2000,windows_xp,windows_2008
  • CPU: other,x86,sparc
  • Submitted: 2005-09-12
  • Updated: 2011-05-18
  • Resolved: 2011-05-18
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 JDK 7
6u25Fixed 7 b06Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Relates :  
Description
OPERATING SYSTEM
Windows XP
FULL JDK VERSION
java version "1.5.0_04"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_04-b05)
Java HotSpot(TM) Client VM (build 1.5.0_04-b05, mixed mode)
DESCRIPTION
The problem relates to the use of the api constructor method
FileInputStream(FileDescriptor), in combination with the class's getFD()
method and its finalizer method. The constructor creates a new
FileInputStream Object based on an existing FileDescriptor Object.
By first creating a FileInputStream using a different constructor,
and then calling getFD(), the FileInputStream(FileDescriptor) 
constructor can be used to create another FileInputStream.
This means we have two FileInputStreams using the same FileDescriptor class, 
and so the same native fd. If one of these objects hten becomes eligible for 
gc, the finalizer can run, which will close the native fd, even though 
the other FileInputStream object is still using it.
A testcase that reproduces this problem is specified below:
==============
import java.io.*;
public class FIS {
  public static void main(String[] args) {
    try {
       /*Create initial FIS for file */
        FileInputStream fis1 = new FileInputStream("thefile.txt");
      /* Get the FileDescriptor from the fis */
        FileDescriptor fd = fis1.getFD();
      /* Create a new FIS based on the existing FD (so the two FIS's share the same native fd) */
        FileInputStream fis2 = new FileInputStream(fd);
      /* allow fis1 to be gc'ed */
      fis1 = null;
      int ret = 0;
      while(ret >= 0) {
        /* encourage gc */
          System.gc();
        /* read from fis2 - when fis1 is gc'ed and finalizer is run, read will fail */
          System.out.println("about to read");
          ret = fis2.read();
      }  
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}
===============
This would be a problem whenever a FileDescripter from any existing Stream
is passed into a FileInputStream or in fact to any Stream which has a
constructor that takes a FileDescriptor and has a finalizer that closes 
the fd (i.e. FileOutputStream would have the same problem.)
This looks like an api issue.
The finalizer seems a sensible thing to have, so that if we forget to close
the FileInputStream, it (and the native fd) are closed at some point. 
However, this logic requires that a native fd is uniquely associated with
a single stream, but this is not guaranteed when you can create a FileInputStream
based on an existing FileDescriptor object(and so existing native fd).

Comments
EVALUATION After many iterations of several approaches, we converged on moving forward with the 2nd option mentioned above that counts the number of streams associated with a FD. The finalize will close the stream only if the count has reached zero. Invoking close() however, will close the stream disregard of the count.
26-12-2006

EVALUATION The submitter is correct. When the first FileInputStream is GC'ed and the finalizer runs then it closes the underlying file descriptor and the associated FileDescriptor is no longer valid. When a read is attempted on the second FileInputStream it doesn't detect this and thus attempts to read on bad file descriptor (-1 in the case of Solaris). Two possible approaches to examine are: 1. Methods on FileInputStream/FileOuptutStream check that the FileDescriptor is valid before doing the I/O. Simple, but there would be timing window similar to an asynchronous close whereby the underlying file descriptor is obtained just at the point that somebody elses closes a stream with the same FileDescriptor. 2. Have FileDescriptor maintain a count of the number of streams that it is associated with. A close (or finalization) would decrement the count the file descriptor would only be closed when the count reaches zero. Relative simple too, but it would be a behaviour change in that calling close wouldn't close the file if there is another stream using the same FileDescriptor.
15-09-2005