views:

99

answers:

4
+1  Q: 

Java file locking

Hi,

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.

Thanks, Don

A: 

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.

Eugene Kuleshov
Yes you are, `lock()` is the same - it calls the method Eugene linked to. See the JavaDoc which I also reference in my answer.
Kevin Brock
+1  A: 

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.

dogbane
I need to lock the file because on Windows you can't lock, delete, or rename a file if it's already in use
Don
@Don, the file is open to call `lock()` so what do you think delete/rename will do? On Windows the same process having the file open will block these operations. What is wrong with using delete/rename directly? If another process has the file open, you cannot lock the file (the way you want) anyway. And locking the file and then deleting it is no better then just deleting it in terms of keeping other processes from accessing the file.
Kevin Brock
A: 

Java file locks are specified only to protect against other locks, and nothing else. How they behave on specific platforms, i.e. any extra semantics, is platform-specific.

EJP
But that doesn't explain why the unit tests shown above fail
Don
What you've said about them protecting only against other locks contradicts what's written here http://www.linuxtopia.org/online_books/programming_books/thinking_in_java/TIJ314_030.htm "The file locks are visible to other operating system processes because Java file locking maps directly to the native operating system locking facility."
Don
No, it doesn't contradict that at all. I said nothing about lock visibility to other processes, or what the locks map to, so there is nothing to be contradicted by your quotation.
EJP
I understood what you said to mean "Having a FileLock on a file, only ensures that nothing else has a FileLock on the same file". That seems to contradict what is written in "Thinking in Java", but maybe I misunderstood you
Don
That's not what I said, is it? I said 'lock', not 'FileLock'.
EJP
+2  A: 

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.

Kevin Brock
Great explanation. Thanks very much for all the effort you put into this. However, your answer still doesn't explain why the `testWrite` unit test fails, any thoughts on that?
Don
@Don, the reason is that your process has the file opened to get the lock (as I stated in my explanation). Specifically the code `new RandomAccessFile(file, "rw")` opens the file.
Kevin Brock
@Don sorry, you're asking about just the one method in your unit test. Let me look at that, it depends on what the other class is doing which is non-standard Java.
Kevin Brock
@Kevin - yes, you said in your response that "writing to the file is a valid lock operation", but my `testWrite` unit test attempts to do this and fails. The other class is not a JDK class (I presume this is what you mean by "non-standard Java"), but it ultimately delegates to the IO classes of the JDK. In other words, it doesn't make any direct system calls, or anything like that
Don
@Don, please read my latest edits, done about/before your comment. You cannot write to a file that has a mandatory lock if that file already is open and locked by another or your own process but using a different file descriptor. The error message is valid. You have already opened the file and locked all possibly bytes. The string write call attempts to open the file a second time and write to it. This must fail since another open (descriptor) holds the locks.
Kevin Brock
@Kevin - if I understand correctly, what you're saying is that: (on windows) opening a channel and locking a file prevents writing to the file (even by the same thread/process). Thanks very much for the clarification, if I could upvote this twice, I would.
Don
@Don - Not quite. If that were true then you couldn't open a file and write to it, but you must open the file to write to it. What I am saying is that you cannot open the file, lock it exclusively and then open it a second time and be able to write to it on the second open/channel (file descriptor).
Kevin Brock