3

I'm writing a command line application in Mac using Objective-c

At the start of the application, i want to check if another instance of the same application is already running. If it is, then i should be either wait for it to finish or exit the current instance or quit the other instance etc.

Is there any way of doing this?

Renjith
  • 83
  • 1
  • 7

3 Answers3

5

The standard Unix solution for this is to create a "run file". When you start up, you try to create that file and write your pid to it if it doesn't exist; if it does exist, read the pid out of it, and if there's a running program with that pid and your process name, wait/exit/whatever.

The question is, where do you put that file, and what do you call it?

Well, first, you have to decide what exactly "already running" means. Obviously not "anywhere in the world", but it could be anything from "anywhere on the current machine" to "in the current desktop session". (For example, if User A starts your program, pauses it, then User B comes along and takes over the computer via Fast User Switching, should she be able to run the program, or not?)

For pretty much any reasonable answer to that question, there's an obvious pathname pattern. For example, on a Mac, /tmp is shared system-wide, while $TMPDIR is specific to a given session, so, e.g., /tmp/${ARGV[0]}.pid is a good way to say "only one copy on the machine, period", while ${TMPDIR}/${ARGV[0]}.pid is a good way to say "only one copy per session".

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Thanks, I have already done this in a similar manner, I was thinking that there would be a better/direct way. I will explain how I did it below. "anywhere on the current machine" is what I needed. – Renjith May 31 '12 at 05:20
  • @abarnert Don't you have a race condition? It is unlikely to happen in *normal* cases, but if a process is started frequently / automatically, the probability of a race condition is higher. – Déjà vu May 31 '12 at 06:30
  • @ring0: yes, a naive implementation has a race condition, but there are multiple ways around that. Basically, they all involve doing things optimistically and retry-looping on unexpected failure. For example, you can do an atomic-write-temp-and-rename while fiddling with permissions so if two processes both try, one of them will fail and retry. Or you can just create and write the file in place; it's possible for one process to see the existing file but not see any contents, in which case it has to retry. And so on. – abarnert May 31 '12 at 17:49
  • The one problem that most common solutions don't take care of is that it's possible for a process to die, leaving its pidfile behind, and then later two new copies start up, and one has the same pid as the abandoned one. You can deal with that by writing the pid plus a launch timestamp, but then you need a way to get the launch time for another process, which is harder to do cross-platform… – abarnert May 31 '12 at 17:51
  • If you are doing to do this, don't mess around with PIDs. Just use `flock` to aquire an exclusive lock on the file. Only one process can aquire the lock. If the lock fails, another process has it. When that process dies, the lock is released. – Mike Weller Aug 13 '12 at 08:21
  • @MikeWeller: The OP wants the option to do things like "quit the original instance". How are you going to handle that with flock? You get an EWOULDBLOCK, and you know someone has it locked, so… now what? Without the pid, there's nothing you can do. – abarnert Aug 14 '12 at 01:34
  • Ah, sorry, my brain apparently decided to overlook that requirement. – Mike Weller Aug 14 '12 at 06:01
  • @MikeWeller: Well, it was worth bringing it up anyway, because for some use cases it is a much simpler answer. – abarnert Aug 14 '12 at 19:25
1

Simple but common way to do this is to check the process list for the name of your executable.

ps - A | grep <your executable name>

Perception
  • 79,279
  • 19
  • 185
  • 195
0

Thank you @abarnert.

This is how I have presently implemented. At the start of the main(), I would check if a file named .lock exists in the binary's own directory (I am considering moving it to /tmp). If it is, application exits.

If not, it would create the file. At the end of the application, the .lock file is removed

I haven't yet written the pid to that file, but I will when exiting the previous instance is required (as of yet I don't need it, but may in the future). I think PID can be retrieved using

int myPID=[[NSProcessInfo processInfo] processIdentifier];

The program will be invoked by a custom scheduler which is running as a root daemon. So it would be run as root.

Seeing the answers, I would assume that there is no direct method of solving the problem.

Renjith
  • 83
  • 1
  • 7
  • There are two reasons to write the pid. First, as you implied, that's how you can kill or otherwise signal the already-running copy (one of the options you asked for in the original question). Second, it lets you distinguish an abandoned file left by a crashed process from a live one (e.g., by doing a kill with signal 0). – abarnert May 31 '12 at 18:02
  • Right, there's no direct way of doing this. There are a wide range of indirect methods, but they all boil down to two things: some kind of predictably-named thing (pidfile, socket, named pipe, NT named kernel mutex), or some kind of broadcast mechanism (Mac distributed notification, Windows WM_ broadcast message, etc.). There are libraries that wrap up the details (see http://stackoverflow.com/questions/380870/python-single-instance-of-program for some Python solutions, for example), although the only C and ObjC ones I know off the top of my head are part of larger frameworks. – abarnert May 31 '12 at 18:10
  • I'm still having issues with the 'lock file' concept. Now I'm thinking of using a ps -ef | grep | grep -vE 'grep' | awk '{print $2}' , then capturing the output and checking the number of lines to know the number of instances and their pid. In that case, I can kill the other instances as well by checking pid!=myPID. I will have to check how to give the command through a NSTask and catch it using NSPipe. Any tips? – Renjith Jun 18 '12 at 12:42
  • Parsing ps is really not a good answer, for a number of reasons. What issues are you having with the "lock file" concept? You may want to take a look at .rc scripts from linux distros to see how they handle the details. – abarnert Jun 19 '12 at 01:43
  • I have a few binaries which are started by another scheduler binary. Some of the binaries have different functionality for different command line argument. Some of these binaries may be executed by the scheduler at the same time based on the repeat interval. In this case, the lock file concept fails randomly. I have the checking for lock file exist and creating lock file in adjacent lines, but if 4 or 5 instances of the binary is started simultaneously, 2 or 3 get past the lock file exist check. Which are the .rc scripts, are you referring to rc.d/init.d scripts or bashrc? – Renjith Jul 04 '12 at 13:33
  • If you can't get lock files to work properly, you may want to open a new question, showing exactly how you're doing it (the usual minimal working code sample), and explaining exactly what doesn't work. – abarnert Jul 05 '12 at 17:49