1

I have a number of separate applications (all written in Java) that need to access a file for short periods of time. Some of these processes will need read access while others will need to update the file. So that all of these applications play nicely I want to write some code that will use OS locks to gain exclusive access to the file for the time that each application requires it.

The obvious way to do this RandomAccessFile myFile = new RandomAccessFile(file, "rw"), however this will fail if another process already has the lock. What I need is the ability to back off and try again.

I was hoping to write some code that uses channel.tryLock() to test if a lock has been taken out. The trouble is I need a channel, and I don't appear able to get that channel object with out taking out a lock!

Update

I need to find a way of checking to see if there is a lock on a file. I want to do this without throwing an exception.

A simplified version of my code is:

void myMethod(File myFile) {
    try (
        RandomAccessFile myFile = new RandomAccessFile(myFile, "rw");  // Exception here
        FileChannel myChannel = myFile.getChannel();
        FileLock myLock = lockFile(myChannel )
    ) {
        // Stuff to read and/or write the file
    }
}

private FileLock lockFile(FileChannel channel) throws Exception {
    FileLock lock;

    while (lock = channel.tryLock() == null) {
        Thread.sleep(100);
    }

    return lock;
}

the problem is that if the file is locked it fails (by throwing an exception) on the highlighted line - before the point that can get a lock for the file.

Other variations for obtaining the channel such as FileChannel channel = FileChannel.open(myFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.APPEND) also throw an exception:

Exception in thread "main" java.nio.file.FileSystemException: path\to\my\file.txt: The process cannot access the file because it is being used by another process.

So how can I get a channel to test the lock with with out throwing an exception?

Stormcloud
  • 2,065
  • 2
  • 21
  • 41
  • 1
    check this link: https://stackoverflow.com/questions/1500174/check-if-a-file-is-locked-in-java – Rafael Jul 28 '21 at 10:42
  • Did you consider using spring retry mechanism for this one? it has an out-of-the-box retry with backoff feature upon an exception – planben Jul 28 '21 at 10:44
  • @planben, This will be deployed in an AWS Lambda function. I don't want all the overheads of Spring in my deployment. Thanks anyway – Stormcloud Jul 28 '21 at 10:52
  • @Rafael, Maybe I'm missing something, but I'm not sure that answers my questions. The accepted answer checks the MS-DOS read-only file attribute. Other answers appear to use exceptions as flow control. Please correct me if I'm wrong – Stormcloud Jul 28 '21 at 10:56
  • https://www.baeldung.com/resilience4j-backoff-jitter – Michael Jul 28 '21 at 11:01
  • 1
    Maybe you should elaborate on “I don't appear able to get that channel object with out taking out a lock”. What have you tried, to what result, etc. As opening the channel and calling `tryLock()` is precisely how it is supposed to work (how else?)… – Holger Jul 28 '21 at 12:30
  • @Holger. Got you - I'll add more detail. Thanks. I don't much like listing things all the thing I've tied that don't work, because I end up with writing a huge question most of which is not relevant to the ultimate answer – Stormcloud Jul 28 '21 at 14:34
  • I am a simple man. When the problem is "a piece of code may throw an exception, what do I do about it", my first thought is... handle the exception. I know that in 99.9% of the cases an exception is simply bubbled up to some generic error handling routine, but there are rare cases where you actually act on them directly... this seems such a case. You can't open the file at this particular moment in time - so deal with that. Bail or make the application wait. – Gimby Jul 28 '21 at 15:24
  • @Gimby, I believe in simple - somebody has to maintain the code so let's make life easy for them/me! In this case another process holding a lock on my file is to be expected, it's not an exceptional case. Rather then throw an exception I need my code to do is wait until the lock is released. I can't I see how to make the application wait. – Stormcloud Jul 28 '21 at 15:43
  • Catch the exception, make the application wait, try again. How else? Your solution seems to a be a thread.sleep() because you don't want to use tried and tested robust mechanics already proposed to you... – Gimby Jul 28 '21 at 15:48
  • @Gimby, please don't get distracted by the code; I needed to add it because somebody marked the question down and if that happens it's not going to get the views I need. I've no objection to replacing my hand rolled retry code with a library, but that's not the point of the question. I want to check if the file is locked WITHOUT throwing an exception (exceptions for flow control is just wrong). The JDK give me access to `channel.tryLock()` that I could use do this *if* only I could get a channel in the first place. Here must be a way because otherwise the tryLock() method is a bit pointless – Stormcloud Jul 28 '21 at 16:30
  • @Holger, You say that "opening the channel and calling tryLock() is precisely how it is supposed to work". Could you please explain this? The point I've got to is that the code either opens a channel and takes the lock, or it throw an exception. This occurs before it gets to `tryLock()`. – Stormcloud Jul 28 '21 at 17:28
  • 1
    There’s no sense in using `FileChannel.open(myFile.toPath(), StandardOpenOption.READ, StandardOpenOption.APPEND)`, as `APPEND` only makes sense for `WRITE`. Since `tryLock`, attempting to get an exclusive lock, requires `WRITE` access either, you’d have to use `FileChannel.open(myFile.toPath(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)`. This works for me under Windows. – Holger Jul 28 '21 at 18:04
  • @Holger I've followed your suggestions, but (under Windows) but FileChannel.open() throws `java.nio.file.FileSystemException: path\to\my\file: The process cannot access the file because it is being used by another process.` when the file is locked. I'll update the question with this detail – Stormcloud Jul 28 '21 at 18:11
  • 1
    What did the other process use? `FileChannel.open` or `RandomAccessFile`? – Holger Jul 29 '21 at 08:45
  • For testing purposes, I just locked it with another application (I opened it in a text editor). my expectation was that these were OS locks so it wouldn't make any difference what the other application was. I just wanted to make it something that wasn't in the current JVM.... I'm guessing from your question that this was the wrong thing to do? – Stormcloud Jul 29 '21 at 10:54
  • 2
    There are different ways to open a file (at least under Windows), with exclusion or without. Normally, Java opens without exclusion, so a different Java process can still open the file and manage exclusion through `FileLock`. There’s the non-standard `OpenOption`, `com.sun.nio.file.ExtendedOpenOption .NOSHARE_WRITE`, that you can specify to `FileChannel.open(…)`. When you do that, the attempt to open the file for writing in another process will fail. It seems, the other non-Java program used a similar mode, preventing the file from being opened by another process at all. – Holger Aug 02 '21 at 17:09
  • @Holger, Thanks for the info. This makes sense; I can to use open with NOSHARE_WRITE to get a channel and then I can use channel.tryLock() to see is somebody else has locked it. This means there is a way to test the lock which is what I was asking for. That said the non-standard mode is a bit worrying. It's almost as if Oracle don't want us to use this API, but it's there so that they can implement the API the do want us to use. If you want credit for this then you could give this an an answer and I'll accept it. – Stormcloud Aug 03 '21 at 10:28
  • 2
    No no, the normal way works by not specifying that option. You’d use this option when you want to prevent other, non-collaborating Windows programs from opening the file. I’ll add an answer to explain this more detailled. – Holger Aug 03 '21 at 10:54

1 Answers1

4

The standard way to use FileLock, is to open the file, e.g. via FileChannel.open, followed by tryLock. The presence of a lock does not prevent other processes from opening the file.

This can be demonstrated by the following program:

import java.io.IOException;
import java.nio.channels.*;
import java.nio.file.*;

class Locking {
    public static void main(String[] args) throws IOException, InterruptedException {
        if(args.length > 0) {
            String me = String.format("%6s ", ProcessHandle.current());
            Path p = Paths.get(args[0]);
            try(FileChannel fc = FileChannel.open(p,
                    StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {

                FileLock l = fc.tryLock();
                if(l == null) System.out.println(me + "could not acquire lock");
                else {
                    System.out.println(me + "got lock");
                    Thread.sleep(3000);
                    System.out.println(me + "releasing lock");
                    l.release();
                }
            }
        }
        else {
            Path p = Files.createTempFile("lock", "test");
            String[] command = {
                Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
                "-cp", System.getProperty("java.class.path"),
                "Locking", p.toString()
            };
            ProcessBuilder b = new ProcessBuilder(command).inheritIO();
            Process p1 = b.start(), p2 = b.start(), p3 = b.start();
            p1.waitFor();
            p2.waitFor();
            p3.waitFor();
            Files.delete(p);
        }
    }
}

which prints something alike

 12116 got lock
 13948 could not acquire lock
 13384 could not acquire lock
 12116 releasing lock

which can be demonstrated online on tio.run

While this program works the same under Windows, this operating system supports opening files unshared, preventing other processes from opening. If a different process has opened the file in that way, we can’t even open it to probe the locking state.

This is not the way, Java opens the file, however, there’s a non-standard open option to replicate the behavior, com.sun.nio.file.ExtendedOpenOption.NOSHARE_WRITE. In recent JDKs, it’s in the jdk.unsupported module.

When we run the following extended test program under Windows

import java.io.IOException;
import java.nio.channels.*;
import java.nio.file.*;
import java.util.HashSet;
import java.util.Set;

class LockingWindows {
    public static void main(String[] args) throws IOException, InterruptedException {
        if(args.length > 0) {
            String me = String.format("%6s ", ProcessHandle.current());
            Path p = Paths.get(args[0]);
            Set<OpenOption> options
                = Set.of(StandardOpenOption.WRITE, StandardOpenOption.APPEND);
            if(Boolean.parseBoolean(args[1])) options = addExclusive(options);
            try(FileChannel fc = FileChannel.open(p, options)) {
                FileLock l = fc.tryLock();
                if(l == null) System.out.println(me + "could not acquire lock");
                else {
                    System.out.println(me + "got lock");
                    Thread.sleep(3000);
                    System.out.println(me + "releasing lock");
                    l.release();
                }
            }
        }
        else {
            Path p = Files.createTempFile("lock", "test");
            String[] command = {
                Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
                "-cp", System.getProperty("java.class.path"),
                "LockingWindows", p.toString(), "false"
            };
            ProcessBuilder b = new ProcessBuilder(command).inheritIO();
            for(int run = 0; run < 2; run++) {
                Process p1 = b.start(), p2 = b.start(), p3 = b.start();
                p1.waitFor();
                p2.waitFor();
                p3.waitFor();
                if(run == 0) {
                    command[command.length - 1] = "true";
                    b.command(command);
                    System.out.println("\nNow with exclusive mode");
                }
            }
            Files.delete(p);
        }
    }

    private static Set<OpenOption> addExclusive(Set<OpenOption> options) {
        OpenOption o;
        try {
            o = (OpenOption) Class.forName("com.sun.nio.file.ExtendedOpenOption")
                .getField("NOSHARE_WRITE").get(null);
            options = new HashSet<>(options);
            options.add(o);
        } catch(ReflectiveOperationException | ClassCastException ex) {
            System.err.println("opening exclusive not supported");
        }
        return options;
    }
}

we will get something like

  2356 got lock
  6412 could not acquire lock
  9824 could not acquire lock
  2356 releasing lock

Now with exclusive mode
  9160 got lock
Exception in thread "main" java.nio.file.FileSystemException: C:\Users\...\Temp\lock13936982436235244405test: The process cannot access the file because it is being used by another process
    at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
    at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:121)
    at java.base/java.nio.channels.FileChannel.open(FileChannel.java:298)
    at LockingWindows.main(LockingWindows.java:148)
Exception in thread "main" java.nio.file.FileSystemException: C:\Users\...\Temp\lock13936982436235244405test: The process cannot access the file because it is being used by another process
    at java.base/sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:92)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:103)
    at java.base/sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:108)
    at java.base/sun.nio.fs.WindowsFileSystemProvider.newFileChannel(WindowsFileSystemProvider.java:121)
    at java.base/java.nio.channels.FileChannel.open(FileChannel.java:298)
    at LockingWindows.main(LockingWindows.java:148)
  9160 releasing lock

The similarity to the outcome of your test suggests that the Windows program you ran concurrently to your Java program did use such a mode.

For your Java programs, no such issue should arise, as long as you don’t use that mode. Only when you have to interact with another Windows program not using the collaborative locking, you have to deal with this.

Holger
  • 285,553
  • 42
  • 434
  • 765