JDK-6266377 : BufferedWriter.close() fails to release resources if exception occurs during flush
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.io
  • Affected Version: 5.0
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_xp
  • CPU: x86
  • Submitted: 2005-05-06
  • Updated: 2011-02-16
  • Resolved: 2005-11-12
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 b61Fixed
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.5.0_01"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_01-b08)
Java HotSpot(TM) Client VM (build 1.5.0_01-b08, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows XP [Version 5.1.2600]

A DESCRIPTION OF THE PROBLEM :
If an exception occurs when closing a BufferedWriter that is writing to a file, the underlying stream does not release its resources correctly which means that subsequent file operations can fail unecessarily.  I can reproduce this always with the attached test case when the destination disk is already full.  The temporary files will be created, but closing the buffered stream will result in an exception due to the lack of disk space available for flushing the buffer.  The next attempt to delete the file fails because the resource hasn't been released properly.  Calling the garbage collector causes the resource to be released correctly.  The problem seems to lie with the implementation of BufferedWriter.close() (and this may also apply to other buffered streams) where there is a call to flushBuffer() before the call to close the underlying writer.  If the call to close() were made in a finally block, then the underlying stream would be closed

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
There are probably lots of ways to recreate this - this is merely one way.  Compile and run the attached test case on a drive where the disk is full.  The test case should be run with a command line like java DeleteFileTest <path>.  Where <path> points to a valid path on a drive that has no more disk space available.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
If there were no bug, then I would not expect any output from the test case at all.
ACTUAL -
java.io.IOException: There is not enough space on the disk
	at java.io.FileOutputStream.writeBytes(Native Method)
	at java.io.FileOutputStream.write(FileOutputStream.java:260)
	at sun.nio.cs.StreamEncoder$CharsetSE.writeBytes(StreamEncoder.java:336)
	at sun.nio.cs.StreamEncoder$CharsetSE.implClose(StreamEncoder.java:427)
	at sun.nio.cs.StreamEncoder.close(StreamEncoder.java:160)
	at java.io.OutputStreamWriter.close(OutputStreamWriter.java:222)
	at java.io.BufferedWriter.close(BufferedWriter.java:244)
	at DeleteFileTest.writeStringToFile(DeleteFileTest.java:47)
	at DeleteFileTest.main(DeleteFileTest.java:15)
File 1 still exists, but should have been deleted
java.io.IOException: There is not enough space on the disk
	at java.io.FileOutputStream.writeBytes(Native Method)
	at java.io.FileOutputStream.write(FileOutputStream.java:260)
	at sun.nio.cs.StreamEncoder$CharsetSE.writeBytes(StreamEncoder.java:336)
	at sun.nio.cs.StreamEncoder$CharsetSE.implClose(StreamEncoder.java:427)
	at sun.nio.cs.StreamEncoder.close(StreamEncoder.java:160)
	at java.io.OutputStreamWriter.close(OutputStreamWriter.java:222)
	at java.io.BufferedWriter.close(BufferedWriter.java:244)
	at DeleteFileTest.writeStringToFile(DeleteFileTest.java:47)
	at DeleteFileTest.main(DeleteFileTest.java:25)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.*;

public class DeleteFileTest {
    private static String text;

    public static void main(String[] args) {
        StringBuffer buf = new StringBuffer(10000);
        for (int i = 0; i < 2000; i++) {
            buf.append("Test ");
        }
        text = buf.toString();
        File file1 = null, file2 = null;
        try {
            file1 = File.createTempFile("firstDeleteTest", ".tmp", new File(args[0]));
            writeStringToFile(file1, text);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            file1.delete();
        }
        if (file1.exists()) System.out.println("File 1 still exists, but should have been deleted");

        try {
            file2 = File.createTempFile("secondDeleteTest", ".tmp", new File(args[0]));
            writeStringToFile(file2, text);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.gc();
            file2.delete();
        }

        if (file2.exists()) System.out.println("File 2 still exists, but should have been deleted");
    }

    private static void writeStringToFile(File file, String text) throws IOException{
        BufferedWriter out = new BufferedWriter(new FileWriter(file));

        try {
            out.write(text);
        } catch (IOException e){
            throw e;
        } finally {
                out.close();
        }
    }

}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The workaround is demonstrated in the test case as well.  Calling System.gc() before attempting to delete the file results in the underlying streams getting cleaned up and thus the delete can succeed.
###@###.### 2005-05-06 09:00:31 GMT

Comments
SUGGESTED FIX *** 237,250 **** } } public void close() throws IOException { synchronized (lock) { ! if (out == null) return; flushBuffer(); out.close(); out = null; cb = null; } } } --- 237,254 ---- } } public void close() throws IOException { synchronized (lock) { ! if (out == null) { return; + } + try { flushBuffer(); + } finally { out.close(); out = null; cb = null; } } + } }
07-11-2005

EVALUATION BufferedWriter.close() should release resources when an exception is thrown during the flushing. See the suggested fix.
07-11-2005