JDK-4829325 : Mapped read prevents file delete
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 1.4.1_01
  • Priority: P3
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2003-03-08
  • Updated: 2003-03-08
  • Resolved: 2003-03-08
Related Reports
Duplicate :  
Description

Name: eaC66865			Date: 03/07/2003


When FileChannel.map() is used to read a file under
windows 2000, the map never goes away, so it prevents
additional operations like File.delete(). Any of those
operations appear to be sufficient under Solaris, 
however. (Solaris may even be closing the channel 
automatically when a File operation is undertaken.)

The following code illustrates the problem. 
closing the channel, closing the stream, and setting
the ByteBuffer to null do not release the map. Only
replacing the map() operation with a read() allows the
File.delete() to work on Windows 2000.

Note:
  * I've run the program on the local file system, and
    the solaris drive mapped to win2k at p:\, with the
    same results.

NioMappingBug.java
------------------
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.charset.*;

import java.util.*;

public class NioMappingBug {
  String testData = "test data";
  File testFile = new File("testFile");

  Charset charset = Charset.forName("UTF-8");
  CharBuffer charBuffer;

  FileChannel testChannel;
  ByteBuffer byteBuffer;

  FileOutputStream fos;

  public static void main(String[] argv) {
    NioMappingBug app = new NioMappingBug();
    app.run();
  }

  public void run() {
    setup();
    writeFile();

    // CHANGE THIS FOR A MAPPED OR UNMAPPED READ
    //   --WHEN TRUE, THE DELETE FAILS ON WINDOWS 2000 (succeeds on
Solaris)
    //   --WHEN FALSE, IT SUCCEEDS ON BOTH SYSTEMS
    boolean mapped = true;

    String result = readFile(testFile, mapped).toString();
    assert result.equals(testData);
    deleteFile();
  }

  public void setup() {
    charBuffer = CharBuffer.allocate(16);
    testChannel = null;
    byteBuffer = null;
    fos = null;
  }

  /**
   * Attempt to write to a file that doesn't exist.
   */
  public void writeFile() {
    createOutputStream();
    assert testFile.exists(); // Creating an output stream also creates
the file
    testChannel = fos.getChannel();
    charBuffer.put(testData);    // position=9, limit=16
    charBuffer.flip();           // position=0, limit=9
    byteBuffer = charset.encode(charBuffer);
    try {
      output(byteBuffer, testChannel);
    }
    catch (IOException ioe) {
      ioe.printStackTrace();
      assert false : "Error writing the file.";
    }
    try {
      testChannel.close();
    }
    catch (IOException ioe) {
      ioe.printStackTrace();
      assert false : "Could not close the newly written file.";
    }
  }

  private void createOutputStream() {
    try {
      fos = new FileOutputStream(testFile);
    }
    catch (IOException ioe) {
      ioe.printStackTrace();
      assert false : "The attempt to create an output stream failed";
    }
  }

  /**
   * This is the <i>right</i> way to write.
   * (Courtesy of Mike McCloskey)
   */
  public void output(ByteBuffer byteBuffer, WritableByteChannel channel)
    throws IOException {
    int bytesWritten = 0;
    while (bytesWritten < byteBuffer.remaining()) {
      // Loop if only part of the buffer contents get written.
      bytesWritten = channel.write(byteBuffer);
      if (bytesWritten == 0) {
        // Media full? We could do some retries here, or throw an
exception
        throw new IOException("Unable to write to channel. Media may be
full.");
      }
    }
  }

  public CharBuffer readFile(File file, boolean mapped) {
    CharBuffer resultBuffer = null;
    try {
      FileInputStream stream = new FileInputStream(file);
      FileChannel fileChannel = stream.getChannel();
      int max = Integer.MAX_VALUE;
      if (fileChannel.size() > max) {
        throw new IOException("File too large: " + file.getPath());
      }
      int size = (int) fileChannel.size();
      if (mapped) {
        // From FileChannel.map:
        // For most operating systems, mapping a file into memory is
more
        // expensive than reading or writing a few tens of kilobytes of
data
        // via the usual read and write methods. From the standpoint of
        // performance it is generally only worth mapping relatively
large files
        // into memory.
        //
        // More importantly, this mapping causes File.delete() to return
false
        // on Windows 2000 (though not on Solaris). There is no unmap()
command,
        // setting byteBuffer to null doesn't help, and closing the
stream (which
        // closes the channel) is also ineffective.
        byteBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0,
size);
      }
      else {
        // This code solves the problem
        byteBuffer = ByteBuffer.allocate(size);
        while (byteBuffer.hasRemaining()) {
          int bytesRead = fileChannel.read(byteBuffer); // increments
position
          if (bytesRead == 0) {
            throw new IOException("Read failure: " + file.getPath());
          }
        }
        byteBuffer.flip();  // position -> 0, limit -> capacity
      }
      resultBuffer = charset.decode(byteBuffer);
      fileChannel.close();
      stream.close();     //closes the associated channel as well
      byteBuffer = null;  // attempt to "unmap" the buffer
      report("   read: |" + resultBuffer.toString() + "|");
      report("ch size: " + size + ", buff capacity: " +
resultBuffer.capacity()
             +  ", pos: " + resultBuffer.position() + ", lim: " +
resultBuffer.limit());
    }
    catch (IOException ioe) {
      ioe.printStackTrace();
      assert false : "readFile: Error reading file: " +
testFile.getPath();
    }
    return resultBuffer;
  }

  public void report(String s) {
    System.out.println(s);
  }

  public void deleteFile() {
    boolean success = testFile.delete();
    if (!success) {
      assert false : "Delete failed: "+ testFile.getPath();
    }
    assert !testFile.exists();
  }

}


======================================================================

Comments
WORK AROUND Name: eaC66865 Date: 03/07/2003 Don't do FileChannel.map() if other file operations are planned on the file, such as File.delete(). Instead, do FileChannel.read(). ======================================================================
11-06-2004