Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does FileLock work?

I've been trying to use FileLock to get exclusive access to a file in order to:

  • delete it
  • rename it
  • write to it

Because on Windows (at least) it seems that you cannot delete, rename, or write to a file that is already in use. The code I've written looks something like this:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

public abstract class LockedFileOperation {

    public void execute(File file) throws IOException {

        if (!file.exists()) {
            throw new FileNotFoundException(file.getAbsolutePath());
        }

        FileChannel channel = new RandomAccessFile(file, "rw").getChannel();

        try {
            // Get an exclusive lock on the whole file
            FileLock lock = channel.lock();

            try {
                doWithLockedFile(file);
            } finally {
                lock.release();
            }
        } finally {
            channel.close();
        }
    }

    public abstract void doWithLockedFile(File file) throws IOException;
}

Here are some unit tests that demonstrate the problem. You'll need to have Apache commons-io on your classpath to run the 3rd test.

import java.io.File;
import java.io.IOException;

import junit.framework.TestCase;

public class LockedFileOperationTest extends TestCase {

    private File testFile;

    @Override
    protected void setUp() throws Exception {

        String tmpDir = System.getProperty("java.io.tmpdir");
        testFile = new File(tmpDir, "test.tmp");

        if (!testFile.exists() && !testFile.createNewFile()) {
            throw new IOException("Failed to create test file: " + testFile);
        }
    }

    public void testRename() throws IOException {
        new LockedFileOperation() {

            @Override
            public void doWithLockedFile(File file) throws IOException {
                if (!file.renameTo(new File("C:/Temp/foo"))) {
                    fail();
                }
            }
        }.execute(testFile);
    }

    public void testDelete() throws IOException {
        new LockedFileOperation() {

            @Override
            public void doWithLockedFile(File file) throws IOException {
                if (!file.delete()) {
                    fail();
                }
            }
        }.execute(testFile);
    }

    public void testWrite() throws IOException {
        new LockedFileOperation() {

            @Override
            public void doWithLockedFile(File file) throws IOException {
                org.apache.commons.io.FileUtils.writeStringToFile(file, "file content");
            }
        }.execute(testFile);
    }
}

None of the tests pass. The first 2 fail, and the last throws this exception:

java.io.IOException: The process cannot access the file because another process has locked a portion of the file
    at java.io.FileOutputStream.writeBytes(Native Method)
    at java.io.FileOutputStream.write(FileOutputStream.java:247)
    at org.apache.commons.io.IOUtils.write(IOUtils.java:784)
    at org.apache.commons.io.IOUtils.write(IOUtils.java:808)
    at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:1251)
    at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:1265)

It seems like the lock() method places a lock on the file which then prevents me from renaming/deleting/writing it. My assumption was that locking the file would give me exclusive access to the file, so I could then rename/delete/write it without worrying about whether any other process is also accessing it.

Either I'm misunderstanding FileLock or it's not an appropriate solution for my problem.

like image 892
Dónal Avatar asked Oct 26 '10 16:10

Dónal


3 Answers

The message about another process just means that some process on your system has the file open. It does not actually check that that process happens to be the same as the one attempting to delete/rename the file. In this case, the same program has the file opened. You have opened it to get the lock. The lock here has little to no value, especially if you are doing this for delete or rename operations.

To do what you want, you would need to lock the directory entry. This is not available in Java and may not be available in Windows. These (delete and insert) operations are atomic. That means that the operating system takes care of locking the directory and other file system structures for you. If another process (or your own) has the file open then these operations will fail. If you are trying to lock the file exclusively (directory entry) and another process (or your own) has the file open, then the lock will fail. There is no difference, but attempting to do the lock just complicates, and in this case, makes the operation impossible (that is, the files are always opened before you attempt to do the operation).

Now writing to the file is a valid lock operation. Lock the file or portion of the file that you want to write to and then it will work. On Windows, this lock mechanism is mandatory so another open/file descriptor will not be able to write to any portion that is under the lock.

EDIT

According to the JavaDoc on FileChannel.lock, it is the same as calling FileChannel.lock(0L, Long.MAXVALUE, false). This is an exclusive lock on a region from the first byte to the last.

Second, according to JavaDoc on FileLock

Whether or not a lock actually prevents another program from accessing the content of the locked region is system-dependent and therefore unspecified. The native file-locking facilities of some systems are merely advisory, meaning that programs must cooperatively observe a known locking protocol in order to guarantee data integrity. On other systems native file locks are mandatory, meaning that if one program locks a region of a file then other programs are actually prevented from accessing that region in a way that would violate the lock. On yet other systems, whether native file locks are advisory or mandatory is configurable on a per-file basis. To ensure consistent and correct behavior across platforms, it is strongly recommended that the locks provided by this API be used as if they were advisory locks.

EDIT

For the testWrite method. The JavaDoc on the commons I/O static method is sparse but says "Writes a String to a file creating the file if it does not exist.." and being as this method takes a File instead of an opened stream, it likely opens the file internally. Probably it is not opening the file with shared access and also opening for append access. This means that the existing open and lock (your open to get the channel from which to get the lock) are blocking that use. To understand even more, you would need to get the source for that method and look at what it is doing.

EDIT

Sorry, I stand corrected. I checked the Windows API and file locking is mandatory on Windows. This is why the write fails. The first open (your new RandomAccessFile) and lock has the file locked. The open to write the string succeeds but the write fails because another open (file descriptor) has the full extent of the file under mandatory exclusive lock - that is, no other file descriptor can write to the file until the lock is released.

Note that locking is associated with the file descriptor NOT process or thread.

like image 59
Kevin Brock Avatar answered Nov 08 '22 03:11

Kevin Brock


The lock you've for is locking a region inside a file, but not the file itself, so while region is locked you can't delete or rename the file.

You may want to look at the Commons Transaction project.

like image 21
Eugene Kuleshov Avatar answered Nov 08 '22 01:11

Eugene Kuleshov


The delete and rename operations are performed by the operating system and are atomic (on most operating systems), so no locking is required.

To write a string to file, it would be simpler to write to a temporary file first (e.g. foo.tmp) and then rename it once it is ready.

like image 1
dogbane Avatar answered Nov 08 '22 02:11

dogbane