Overview

When main applications are launched, it is often useful to not allow multiple instances to be open at the same time. For example, consider two editors opened on the same file. Making changes in one will invalidate the other.

The solution for this is to implement applications as singletons -- that is, only one instance can be alive at the same time.

There are two strategies to achieve this. One is to abort a second instance on startup if a first is alive and active. (Call this strategy "defer".) The other is to have the second instance automatically close the first. (Call this strategy "preempt".)

Note that it is necessary for the application to be primarily invoked via the run() method. Thus, it is a good idea that it extends Runnable.

Usage

The Simple Case

The majority of the logic is defined in the abstract base class AbstractSingleApp.

To use it, either the main application class should extend it, or a helper class which launches the main class should extend it. The top-level run() method of the application will be invoked by this framework.

The main() method should instantiate the AbstractSingleApp extension class. Then, depending on which strategy is desired, it should call either launchDefer(portnumber) or launchPreempt(portnumber). Notice that it is necessary to use a free network port because the framework uses socket communication to interact (more details on this below).

If the "defer" strategy is used, then no other logic needs to be implemented.

If the "preempt" strategy is used, then the gracefulExitForPreempt() method must be overridden. This method must force the first invocation to exit (in order to allow the next invocation to take over).

The General Case

The advantage of using AbstractSingleApp is that it provides stub implementations of the required interface so extenders need only override a minimal set.

The general case is to use SingleAppEnforcer to drive the launching, and have the main class implement the interface SingleApp. Using AbstractSingleApp simply encapsulates these two aspects.

Mechanism

The framework performs inter-process communication via sockets. This is why a port number is required to be passed to one of the "launch" methods.

Whenever an application starts (regardless of whether it is using the defer or preempt) it attempts to start a server listening on the relevant port. If no other instance of the application is alive, then this will happen without conflict. At this point, it will perform the "take control" action and start listening for messages (more details on these messages below) on that port, and then it will exectute the main action of the application (via the "run()" method).

If another instance of the application is already alive, then the attempt to create a socket listener will fail because of the port conflict with the previous instance. When happens next depends on whether the defer strategy or the preempt strategy is specified.

If a conflict is detected with the defer strategy, then the action taken is to send a message to the extant instance and terminate. (The termination is implicit since there are no other actions performed by the deferred application. That is, it never calls the run() method, but just finishes execution.)

If a conflict is detected with the preempt strategy, then the action taken is to read a message from the soon-to-die previous instance, wait a fixed period of time for it to die, then perform the "take control" action of listening for messages.

Messaging

The act of exchanging messages mentioned above serves two purposes. First it defines the handshake mechanism that ensures the proper timing of actions. Second, it allows the exchange of information between the two applications so that proper state is preserved.

For an example of the utility of the message payload, consider an example where the application is a web browser using the preempt strategy. If a new instance will be replacing an older one, the previous instance will want to send the new one the web page that it is currently viewing so that the new one starts in the same state.

Note that the act of handling an incoming message is also dependent on which strategy is used. For the defer strategy, the message is read (from the newer instance that will be aborted), and if there is a payload, then that is processed. For the preempt strategy, a message is written (to the new instance that will replace it), then the method that defines graceful exit is called, and then the application is terminated.


Project Overview