Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java FileChannel.size() vs File.length() - After FileChannel.truncate()

Tags:

java

filesize

I was thinking about changing this question into my situation. I then decided that my situation needed its own question and hopefully answers. After calling FileChannel.truncate() to reduce the size of the file, I call FileChannel.size(), close the FileChannel and then call File.length(). The File exists throughout the entire operation. FileChannel.size() is always accurate. On a rare occasion, File.length() will return the size of the file before truncate().

Here is the code that shows the situation.

public static void truncate(File file, long size) throws IOException
{
   FileChannel channel;
   Path path;
   long channelSize, fileLengthOpen, fileLengthClosed;

   path    = file.toPath();
   channel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

   try
   {
      channel.truncate(size);

      channelSize    = channel.size();
      fileLengthOpen = file.length();
   }
   finally
   {
      channel.close();
   }

   fileLengthClosed = file.length();

   if ((channelSize != size) || (fileLengthOpen != size) || (fileLengthClosed != size))
      throw new IOException("The channel size or file length does not match the truncate size.  Channel: " + channelSize + " - Open File: " + fileLengthOpen + " - Closed File: " + fileLengthClosed + " - Truncate: " + size);
}

On a rare occasion, the code throws the IOException. channelSize == size and fileLengthOpen == size but fileLengthClosed != size.

Why does fileLengthClosed != size? How do I ensure File.length() matches FileChannel.size()? Flushing the file's metadata would be okay as long as it does not force flushing the file's contents. Flushing the file's contents would incur unneeded file I/Os.

Answers telling me to use channelSize or fileLengthOpen or to ignore the problem will not be accepted. I have another class file that uses File.length() to determine the length of the file. Passing channelSize or fileLengthOpen to the other class would require passing the value up the stack several frames and then back down several frames. If you suggest that I use Files.size() to fix the problem, please explain why.

I am not sure if this matters. I am running Java 10 on Linux. (Yeah, I know Java 10 is old, but I am stuck with it until I can implement the necessary features to upgrade to Java 11.)

Edit: In the past, multi-threaded updates on files has caused problems. For this reason, I created a file locking mechanism so that only 1 thread in the entire process can exclusively operate on a file (or multiple threads can read the file). Furthermore, the File object passed to my truncate() was created by the calling thread and is only used by that thread. I can guarantee that no other thread in the process can operate on the file on disk or the File object.

Edit: I changed the code to call Files.size() before and after channel.close(). channel.size(), File.length() and Files.size() report the correct size while channel is open. Right after channel.close(), File.length() and Files.size() are on the rare occasion the original longer file length and not the truncated shorter length.

like image 524
Nathan Avatar asked Nov 07 '22 18:11

Nathan


1 Answers

Spin wait for fileLengthClosed to be correct. Here is the code.

while (true)
{
   fileLengthClosed = file.length();

   if (fileLengthClosed == size)
      break;

   Thread.sleep(1);
}

I have run this code for 3 weeks and there have been no issues. One concern is that there is no timeout code in case the file length is modified or the file.length() never updates.

This solution does not answer why File.length() is incorrect in the first place. Also, calling Thread.sleep() suggests that I am really waiting for some other operation to complete and I really should force that operation to complete or block on that operation. I am not sure how to force or block on that operation.

like image 103
Nathan Avatar answered Nov 14 '22 23:11

Nathan