3

currently I am struggling with the problem of a single instance JavaFX application, packed into an .exe using install4j. The application should run on a Windows terminal server and every user should only be able to run one instance of it. Meaning, Alice and Bob may use separate instances of the application but Alice may only have one instance open.

Writing a lock file with the process id is not a viable option, since the application is targed at Java 8, which has no consistent possibility to retrieve the process id. Opening a socket is also not a desirable solution, as there can be many instances on the same host. Moreover I suppose admins would not be that happy if some application randomly opened sockets on their server...

As I am using install4j to pack the application, I toggled the 'single instance only' feature which seems to run well when connected via a full RDP session. However, the application may be deployed using the RemoteApp feature which in some way circumvents install4j's checking mechanism, allowing one instance to be launched in a RDP session and another by using the RemoteApp.

This leads me to two questions:

  1. How does the install4j check work? (I was not able to find any details...)
  2. What would be the best solution to ensure a single instance per user at all times? (And also be failsafe, e.g. recover from JVM crashes)
  3. Regarding the possibility of FileLock: as different operating system may handle file locks differently, can it be assured that the file lock is exclusively acquired by one JVM instance on the whole system?
5
  • Why does the lock file need to have the process id? It is not going to be the case that the second process will have the same process id anyway Commented Oct 22, 2018 at 14:29
  • If the JVM crashes I cannot delete the lock file. As a consequence the application could not start ever again, unless the user manually deletes the lock file Commented Oct 22, 2018 at 14:30
  • Not really. The OS will release the lock on the file once the process dies. It is standard behaviour on most operating systems. All you need is acquire a file lock on startup, irrespective of whether the file already exists or not (you just create it if it does not). Commented Oct 23, 2018 at 10:22
  • That depends on how you use the file for locking. Either you can check for the existence of the file or you try to acquire a lock. If you check for existence you need the pid to determine if the process is still alive. I added a third point my worries with the file lock approach Commented Oct 23, 2018 at 11:58
  • Yes, the way it is usually done is that you check if the file exists, possibly try to delete it (that way you are extra sure there is no process even trying to read from it), then recreate it and lock it. Commented Oct 23, 2018 at 13:11

3 Answers 3

1

Sockets will be a bit problematic if you want the application to run concurrently under different users.

The option of using an NIO FileLock is possible. You create the file under the user's directory so that another user can have his own lock file. The key thing to do here is to still try to acquire the file lock if the file exists already, by attempting to delete it before recreating it. This way if the application crashes and the file is still there, you will still be able to acquire a lock on it. Remember that the OS should release all locks, open file handles and system resources when a process terminates.

Something like this:

 public ExclusiveApplicationLock
     throws Exception {

   private final File file;
   private final FileChannel channel;
   private final FileLock lock;

   private ExclusiveApplicationLock()  {

       String homeDir = System.getProperty("user.home");

       file = new File(homeDir + "/.myapp", app.lock");
       if (file.exists()) {
          file.delete();
       }

       channel = new RandomAccessFile(file, "rw").getChannel();
       lock = channel.tryLock();
       if (lock == null)  {
          channel.close();
          throw new RuntimeException("Application already running.");
       }

       Runtime.getRuntime().addShutdownHook(new Thread(() -> releaseLock());            
  }

  private void releaseLock() {
    try {
      if (lock != null) {
        lock.release();
        channel.close();
        file.delete();
      }
    }
    catch (Exception ex) {
       throw new RuntimeException("Unable to release application process lock", ex);
    }
  }
}

Another alternative is to use a library that does this for you like Junique. I haven't tried it myself but you could have a go. It seems very old but I guess there isn't much that needs to change in something like this, nothing much changed in NIO since Java 1.4.

http://www.sauronsoftware.it/projects/junique/

It is on Maven Central though so you can import it easily. https://mvnrepository.com/artifact/it.sauronsoftware/junique/1.0.4

If you look at the code you will see that it does the same thing with file locks: https://github.com/poolborges/it.sauronsoftware.junique/blob/master/src/main/java/it/sauronsoftware/junique/JUnique.java

Sign up to request clarification or add additional context in comments.

4 Comments

Yeah, I had a look at JUnique before but discarded it because it seemed unmaintained. However, you are probably right, that there is not much to do. I will give this a go.
But still, since Java Applications should be platform independent: is there a possibiltiy that the FileLock approach will not work on some platform? For example if the file lock ist treated as advise (like the JavaDoc implies)?
@Rosso which platforms are you targeting? I guess if you want to be 100% sure you have to test it on the platforms you want to support. Yes it does say that on some platforms file locking is not possible, but I guess that is also because file locking is something native, so they can't possibly guarantee it on all possible JVM implementations. On the other hand, I don't know of any modern OS that does not work in this way, but I could be wrong.
That is more a general question. For my use case (Windows/mac) it suits perfectly well, was just wondering if there was anything well known out there
0

As for 1: On Windows, install4j launchers create a semaphore with the CreateSemaphore function in the Windows API. You can check the name of the semaphore by executing the launcher from the command line with the

/create-i4j-log

argument.

5 Comments

Thanks for your reply. This leads me to the conclusion that the semaphore is somehow session based in Windows? We decided to stick with the install4j approache, accepting the flaw of two instances with RDP session and RemoteApp
Moreover, is this documented somewhere with install4j? Otherwise, could you point out how it works for mac and linux for reference? Except if this is a implementation detail and very likely to change
Yes, this is session-based on Windows. On macOS and Unix it is user-based and uses file locking.
@IngoKegel any chance we can get a user-based semaphore in install4j, to enable the "single instance per user" scenario? I would love to use install4j for it, because the launcher API already allows obtaining the parameters (startupPerformed). At the moment I do it manually (see answer below)
@AlexSuzuki I have added this to our issue tracker for 9,0
0

I faced the same issue, and solved it by using a FileLock like the other answer.

In my case, the arguments that are passed to the launched processes needed to be forwarded to the first process. For this, I used a named pipe, which includes the username in its name. The first process creates the named pipe at \.\pipe\app_$USER. If the same exe is is started by the the same user, it is detected by the FileLock, and the agruments are passed through the named pipe.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.