13

I came across an issue with one of our utility classes today. It is a helper for files and contains some static file copy routines. Below are the relevant methods extracted along with a test method.

The problem is that sometimes the setLastModified call fails, returning false.

On my PC (Windows 7, latest Java) I sometimes get the "setLastModified failed" message (About 25 times out of 1000).

I have worked around the problem right now by removing the FileChannel.close calls but I would much prefer to understand why this is happening, even if that is the correct solution.

Does anyone else get the same problem?

private void testCopy() throws FileNotFoundException, IOException {
  File src = new File("C:\\Public\\Test-Src.txt");
  File dst = new File("C:\\Public\\Test-Dst.txt");

  for (int i = 0; i < 1000; i++) {
    copyFile(src, dst);
  }
}

public static void copyFile(final File from, final File to) throws FileNotFoundException, IOException {
  final String tmpName = to.getAbsolutePath() + ".tmp";
  // Copy to a .tmp file.
  final File tmp = new File(tmpName);
  // Do the transfer.
  transfer(from, tmp);
  // Preserve time.
  if (!tmp.setLastModified(from.lastModified())) {
    System.err.println("setLastModified failed!");
  }
  // In case there's one there already.
  to.delete();
  // Rename it in.
  tmp.renameTo(to);
}

public static void transfer(final File from, final File to) throws IOException {
  FileInputStream in = null;
  FileOutputStream out = null;
  try {
    in = new FileInputStream(from);
    out = new FileOutputStream(to);
    transfer(in, out);
  } finally {
    if (null != in) {
      in.close();
    }
    if (null != out) {
      out.close();
    }
  }
}

public static void transfer(final FileInputStream from, final FileOutputStream to) throws IOException {
  FileChannel srcChannel = null;
  FileChannel dstChannel = null;
  //try {
    srcChannel = from.getChannel();
    dstChannel = to.getChannel();
    srcChannel.transferTo(0, srcChannel.size(), dstChannel);
  //} finally {
  //  if (null != dstChannel) {
  //    dstChannel.close();
  //  }
  //  if (null != srcChannel) {
  //    srcChannel.close();
  //  }
  }
}

Edit: I have changed the code to only close the Streamss and not the FileChannels because research suggests closing the FileChannel also closes the Stream.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • The test file I am using is about 4mb. – OldCurmudgeon Nov 09 '11 at 14:05
  • 1
    I have noticed in your transfer method where you use the streams and the channels that you have no catch block. So I'm assuming if there is an IOException, you reach the finally block and try to close the channels. There may be another IOException there, which could be the one thrown by your method. Doesn't answer your question, but I would think it would be cleaner to rethrow the original Exception and put try catch blocks with empty catches aroudn your channel closings. – Chris Aldrich Nov 09 '11 at 14:09
  • Good point Chris, and well spotted. I have actually pared the code down to get rid of unnecessary distractions. There is actually a little more exception handling going on in the real code. – OldCurmudgeon Nov 09 '11 at 14:30
  • I got the same behaviour - once. That looked like a kind of congestion: several failures one after the other. Try with unique names, no delete/renameTo. It might be that Windows is interfering in the background (virus scanner/search engine indexing). Not reassuring though. – Joop Eggen Nov 09 '11 at 14:57
  • @Joop _I got the same behaviour - once_ Was that with my test code above or are you saying you have seen this issue before? – OldCurmudgeon Nov 09 '11 at 16:10
  • `transferTo` is unsupported on Windows, i.e. it's emulated and this is why it always transfer the entire file, on Linux it may not transfer everything and it shall loop. The only way I see setModified to fail is if the file is locked. Also try w/ unique file name to exclude any concurrent code. – bestsss Nov 21 '11 at 12:52

2 Answers2

11

After some research amongst the various sites that hold java library sources it looks very much like FileChannel.close ultimately calls the FileInputStream.close or FileOutputStream.close of its parent object.

This suggests to me that you should either close the FileChannel or the Stream but not both.

In view of this I am changing my original post to reflect one correct method, i.e. close the Streams not the Channels.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • you should close the streams, i.e. what you have opened. but yes FileChannelImpl (if you are using Sun's VM) does call the parent's `close()`. – bestsss Nov 21 '11 at 12:13
  • @bestess: You are correct! My apologies - I actually did this in the code but forgot to change my post here. – OldCurmudgeon Nov 21 '11 at 12:43
3

If you are using Java 7, you could use Files.copy(Path source, Path target, CopyOption... options) for this operation to avoid writing, testing and debugging your own implementation.

Alternatively, consider using an external library such as Apache Commons IO. Specifically, you will find FileUtils.copyFile(File srcFile, File destFile) interesting:

/** 
 * Copies a file to a new location preserving the file date.
 * [...]
 * @param srcFile  an existing file to copy, must not be <code>null</code>
 * @param destFile  the new file, must not be <code>null</code>
 * [...]
 */
public static void copyFile(File srcFile, File destFile) throws IOException
matsev
  • 32,104
  • 16
  • 121
  • 156
  • Thanks matsev, and I will certainly be moving over to Java7 as soon as possible. The strange thing is that this code has been in our utilities libraries for a long time. I only discovered the issue when I started checking the return value to setLastModified. Did you run the code? Did it report errors? – OldCurmudgeon Nov 14 '11 at 09:02
  • No, I have not tried your implementation. We are using Java 6 in our current project and we let [FileUtils](http://commons.apache.org/io/api-release/org/apache/commons/io/FileUtils.html) handle our file operations and its sibling [IOUtils](http://commons.apache.org/io/api-release/org/apache/commons/io/IOUtils.html) handle generic `InputStream`, `OutputStream`, `Reader` and `Writer` operations. – matsev Nov 14 '11 at 17:15
  • 1
    This is a useful advice, but hardly an answer. You should've left this as a comment. – Malcolm Jul 27 '12 at 17:12