Name: jl125535 Date: 11/11/2003
FULL PRODUCT VERSION :
Observed on:
java version "1.3.1_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1_01)
Java HotSpot(TM) Client VM (build 1.3.1_01, mixed mode)
And
java version "1.4.2_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_01-b06)
Java HotSpot(TM) Client VM (build 1.4.2_01-b06, mixed mode)
FULL OS VERSION :
SunOS ourhostname 5.8 Generic_108528-01 sun4u sparc SUNW,Ultra-30
Haven't tested on Linux or Windows, but it may also be a problem there.
A DESCRIPTION OF THE PROBLEM :
The C close(fd) call can fail, returning -1. When closing a FileOutputStream, this error is not propagated via a thrown IOException.
This can be easily reproduced by creating a file on an NFS server, writing data, then pausing for console input. Unplug the connection to the NFS server, then press return. Close the FileOutputStream, catching any IOException from the close and displaying it. You won't see one; its a silent failure.
The same code in C will return -1 from the close with an appropriate error in errno.
The "severity" field below doesn't have an option for "Data Loss", but that's what this means for us.
Note that our actual case doesn't involve NFS, but a custom filesystem of ours which can return -1 on close. However, the problem can be easily reproduced as explained above.
Looking through the 1.3.1 (and 1.4.1) source, I see:
j2sdk1.3.1/src/share/classes/java/io/FileOutputStream.java -
public native void close() throws IOException;
This calls....
j2sdk1.3.1/src/share/native/java/io/FileOutputStream.c -
JNIEXPORT void JNICALL
Java_java_io_FileOutputStream_close(JNIEnv *env, jobject this) {
int fd = GET_FD(fos_fd);
SET_FD(-1, fos_fd);
JVM_Close(fd);
}
Should that last line read:
if (JVM_Close(fd) == -1)
JNU_ThrowIOExceptionWithLastError(env, "close failed");
?
(Also scanned through the source for other calls to JVM_Close(fd), and didn't see the return being checked on any of them....)
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
On your workstation, run the attached java or C test, passing as an argument the name of a file on a remote NFS (soft-mounted) server. When prompted "continue:", disconnect your workstation from the network, then press return.
After the NFS timeout...
With the C program, you should see "close() failed".
With the Java program you will see "close() succeeded".
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I'd expect an appropriate IOException being thrown, as the API for FileOutputStream.close() suggests.
ACTUAL -
# this is the java version of the test program...
[cjones@rhino] java iotest abc.j
open(abc.j) succeeded
continue: y
NFS commit failed for server thelma: error 5 (RPC: Timed out)
close() succeeded
# this is the C version...
[cjones@rhino] ./raw abc.r
raw: open(abc.r) succeeded
continue: y
NFS commit failed for server thelma: error 5 (RPC: Timed out)
raw: close() failed: Connection timed out
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
---------- Java version ------------
import java.io.FileOutputStream;
import java.io.IOException;
public class iotest
{
public static void main(String[] args)
{
if (args.length != 1)
{
System.err.println("usage: test file");
System.exit(1);
}
FileOutputStream outputStream = null;
try
{
outputStream = new FileOutputStream(args[0]);
}
catch (IOException e)
{
System.err.println("open() failed: " + e.getMessage());
System.exit(2);
}
System.out.println("open(" + args[0] + ") succeeded");
byte[] buf = new String("01234567890\n01234567890\n").getBytes();
for (int i = 0; i < 10; i++)
{
try
{
outputStream.write(buf);
}
catch (IOException e)
{
System.err.println("write() failed: " + e.getMessage());
break;
}
}
byte[] response = new byte[2];
System.out.print("continue: ");
try
{
System.in.read(response);
}
catch (IOException e)
{
System.err.println("read() failed: " + e.getMessage());
}
try
{
outputStream.close();
System.out.println("close() succeeded");
}
catch (IOException e)
{
System.err.println("close() failed: " + e.getMessage());
}
}
}
-------- C version -----------
#include <libgen.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
static char buf[] = "0123456789\n0123456789\n";
char *progname = basename(argv[0]);
int output;
ssize_t n;
char c;
int i;
if (argc != 2)
{
(void) fprintf(stderr, "usage: %s file\n", progname);
return 1;
}
if ((output = open(argv[1], (O_WRONLY|O_TRUNC|O_CREAT), 0666)) < 0)
{
(void) fprintf(stderr, "%s: open(%s) failed: %s\n", progname, argv[1], strerror(errno));
return 2;
}
(void) fprintf(stderr, "%s: open(%s) succeeded\n", progname, argv[1]);
for (i = 0; i < 10; i++)
{
if ((n = write(output, buf, 22)) < 0)
{
(void) fprintf(stderr, "%s: write() failed: %s\n", progname, strerror(errno));
break;
}
if (n < 22)
{
(void) fprintf(stderr, "%s: short write(): %d bytes written\n", progname, n);
break;
}
}
(void) fputs("continue: ", stdout);
c = getc(stdin);
if (close(output) < 0)
{
(void) fprintf(stderr, "%s: close() failed: %s\n", progname, strerror(errno));
return 2;
}
(void) fprintf(stderr, "%s: close() succeeded\n", progname);
return 0;
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
I'd like to have been able to call FileOutputStream.getFD(), get the int fd from that, then go native myself, and throw the IOException if close(fd) fails. Pity the fd is private.
(Incident Review ID: 207606)
======================================================================