Question

I was creating an application in Java for which I want only one instance running. For this purpose I created a file and got a lock while my application is running.

I have following code which works on Windows, but failed on Linux: once I acquire a lock without unlocking it I can get another lock on it.

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

public class MyApp {

private static File f;
private static FileChannel channel;
private static FileLock lock;

public static void main(String[] args) {
    try {
        f = new File("RingOnRequest.lock");
        // Check if the lock exist
        if (f.exists()) {
            // if exist try to delete it
            f.delete();
        }
        // Try to get the lock
        channel = new RandomAccessFile(f, "rw").getChannel();
        lock = channel.tryLock();
        if(lock == null)
        {
            // File is lock by other application
            channel.close();
            throw new RuntimeException("Only 1 instance of MyApp can run.");
        }
        // Add shutdown hook to release lock when application shutdown
        ShutdownHook shutdownHook = new ShutdownHook();
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        //Your application tasks here..
        System.out.println("Running");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    catch(IOException e)
    {
        throw new RuntimeException("Could not start process.", e);
    }

}

public static void unlockFile() {
    // release and delete file lock
    try {
        if(lock != null) {
            lock.release();
            channel.close();
            f.delete();
        }
    } catch(IOException e) {
        e.printStackTrace();
    }
}

static class ShutdownHook extends Thread {

    public void run() {
        unlockFile();
    }
}

}
Was it helpful?

Solution

I used same sample as you and got same problem on Mac OS X. It seems that file lock does not prevent file deletion on POSIX systems . Your app wil still have some kind of handle to that file until you unlock it. So consider using lock file with PID in it's name( or inside file).

OTHER TIPS

You are deleting the lock file every time you run, so only one process can have a lock on it.

When you use FileLock, it is purely advisory—acquiring a lock on a file may not stop you from doing anything…reading, writing, and deleting a file may all be possible even when another process has acquired a lock. Sometimes, a lock might do more than this on a particular platform, but this behavior is unspecified, and relying on more than is guaranteed in the class documentation is a recipe for failure.

An "advisory lock" is only a signal that is visible to other processes that bother to look for it. If you depend on it for more than that, your program will break when run on some other platform.

Why would you delete the lock file anyway? The lock file is just like a boolean flag that is visible to every process on the system. Design your protocol to use it that way, and you'll have a reliable, cross platform locking mechanism.

Why don't you save the PID into a file, and instead of locking the file, verify if there's a process with that ID. If there is, and it's an instance of your application, you know it's already running.

A socket might be a good idea as well, since you can use it to communicate to the running instance something.

EDIT:

Also, from FileLock's javadoc:

Whether or not a lock actually prevents another program from accessing the content of the locked region is system-dependent and therefore unspecified.

Use mkdir. On unix systems this is an atomic operation – it will succeed if a new directory is successfully created, otherwise it will fail.

Example:

File lockFile = new File("/path/to/lockdir");
boolean hasLock = lockFile.mkdir();
if (!hasLock) {
  throw new IOException("could not get lock");
}
// do stuff
lockFile.delete();

I tested it on both Windows and Linux. Works fine. The lock file gets deleted automatically when the application closes normally. So you don't have to worry about the lock file staying there when you restart the application. Just comment out the following lines:

if (f.exists()) {
    // if exist try to delete it
    f.delete();
}

However, you may want to consider what happens if your application crashes and does not close in a normal fashion.

Recently i encountered the same kind of problem, but in my case i had an advantage: my application polled some directory only after some timeout. As my application did not immediately poll for directory i wrote special class that creates lock file with his own PID inside in init method, after that before it tries to work with directory it needs to call ownedLock() - if it returns true then we can work otherwise exit(code is in Kotlin but you will get the main idea):

import java.io.File
import java.lang.management.ManagementFactory
class DirectoryLocker(private val directory: String, private val lockName: String) {
   private val lockFile by lazy { File("$directory/$lockName.lock") }

   // Will try to acquire lock to directory, whoever last writes its pid to file owns the directory
   fun acquireLock() = with(lockFile) {
      createNewFile()
      writeText(currentPID())
   }

   fun ownedLock(): Boolean = lockFilePid() == currentPID()

   fun releaseOwnedLock() {
      if(lockFilePid() == currentPID()) lockFile.delete()
   }

   private fun currentPID(): String {
      val processName = ManagementFactory.getRuntimeMXBean().name
      return processName.split("@".toRegex()).first()
   }

   private fun lockFilePid(): String? {
      return if(lockFile.exists()) lockFile.readLines().first() else null
   }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top