Reputation: 136042
At first glance this code seems completely OK
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream("1.txt"));
byte[] bytes = new byte[4096];
bout.write(bytes);
bout.close();
but if we take a closer look we will see that close()
is implemented as follows
public void close() throws IOException {
try {
flush();
} catch (IOException ignored) {
}
out.close();
}
Is it possible that due to flush()
errors are ignored data may be lost and the program will not notice it? There is no mentioning of any danger in FilterOutputStream.close
(where BufferedOutputStream
inherits close()
from) API.
UPDATE: To simulate IO error during close() I changed the test to write to a Flash memory, added a 5 secs sleep before bout.close() and while the test was sleeping I removed the Flash from USB. The test finished without exceptions, but when I inserted the Flash and checked it - 1.txt was not there.
Then I overrode close()
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream("g:/1.txt")) {
@Override
public void close() throws IOException {
flush();
super.close();
}
};
and ran the test again and got
Exception in thread "main" java.io.FileNotFoundException: g:\1.txt (The system cannot the specified path)
at java.io.FileOutputStream.open(Native Method)
at java.io.FileOutputStream.<init>(FileOutputStream.java:212)
at java.io.FileOutputStream.<init>(FileOutputStream.java:104)
at test.Test1.main(Test1.java:10)
Upvotes: 19
Views: 3996
Reputation: 38777
In OpenJDK 11, the FilterOutputStream#close()
method (which BufferedOutputStream
inherits) is a lot more complex:
public void close() throws IOException {
if (closed) {
return;
}
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
Throwable flushException = null;
try {
flush();
} catch (Throwable e) {
flushException = e;
throw e;
} finally {
if (flushException == null) {
out.close();
} else {
try {
out.close();
} catch (Throwable closeException) {
// evaluate possible precedence of flushException over closeException
if ((flushException instanceof ThreadDeath) &&
!(closeException instanceof ThreadDeath)) {
flushException.addSuppressed(closeException);
throw (ThreadDeath) flushException;
}
if (flushException != closeException) {
closeException.addSuppressed(flushException);
}
throw closeException;
}
}
}
}
As you can see, if the flush()
method throws then the exception is now correctly propagated to the caller.
Together with the added locking, BufferedOutputStream
should now be as safe as possible.
Upvotes: 0
Reputation: 15990
As it is, I would reason that calling close
can indeed make you lose data, since that potential IOException
is being silently ignored (who on earth knows what went through the developers minds to do that...).
A decent alternative, although it does put the effort on the side of the programmer, is to call flush
explicitly before close
(handling the potential IOException
correctly), like mentioned in a comment by @Tom, particularly in a try/finally
block.
This problem can be further exacerbated in Java7, due to AutoCloseable
objects, since you won't explicitly call the close()
method and this kind of work-around is even easier to slip by.
Upvotes: 6