| Main |

Cross-process semaphores with timeouts on OS X

Someone on darwin-dev recently asked how to go about obtaining a cross-process semaphore that can be waited on with a timeout on OS X.

POSIX semaphores currently don’t support this feature on OS X; nor do System V semaphores. Mach semaphores do support timeouts (see /usr/include/mach/semaphore.h), but it isn’t immediately obvious how to pass one to another process.

Anyway, I thought I’d stick together a simple Mach server to implement named semaphores; the idea is very simple… when you want a semaphore, you ask the server to create it for you, giving it a name. If it already exists, the server will simply return the existing semaphore object. If not, it will create a new Mach semaphore and return that.

Once you have the semaphore_t port value, you use it just as if you had called semaphore_create() yourself. When you’re done with it, you can use mach_port_destroy() to release the port in your own process. You won’t be able to destroy the semaphore using semaphore_destroy(), because your task doesn’t own it.

A few notes:

  1. If you’re going to use this code directly in your program, please don’t change the .defs file without altering the definition of SEMSRV_SERVICE to a name that starts with your own reverse domain prefix. Doing that would break anything that uses this code literally as-is.

  2. In a normal program, you probably want to try spawning the semsrv process in the background. If another program has already started the server with the same service name, your copy will fail, but that’s OK because you can just use the other copy.

  3. The semsrv program doesn’t daemonize itself. This might matter to you, or it might not.

  4. It’s possible that you might be interested in re-writing the server to use launchd so that it starts automatically and you don’t need to worry about point 2 (above). Note though that doing that will mean your program needs to install a suitable launch daemon property list, which might be more of a headache than it’s worth.

  5. This code is only really intended as a sample, and has had limited testing. There is obviously no warranty or anything like that.

  6. The code is in the Public Domain. It is not subject to copyright and therefore has no license. If you work for someone too stupid to understand the words Public Domain, you can simply slap a BSD or MIT license on it and carry on with what you were doing.

The code can be built by typing make at a command prompt after changing into its directory. It has a very simple (and not perfect) Makefile, but I can’t be bothered with anything more elaborate. You could add the files to an Xcode project instead if you wanted, in which case you’d need to make Xcode run mig if it doesn’t already know to do that.

To try a few simple experiments, build the code, then enter ./semsrv & at a Terminal prompt to start the server. You can then use the ./semtest program to try various things, e.g.

$ tar xjf semsrv.tar.bz2
$ cd semsrv
$ make
mig semsrv.defs
cc -g -W -Wall   -c -o semsrvServer.o semsrvServer.c
g++ -g -W -Wall   -c -o server.o server.cc
g++ -g -W -Wall  semsrvServer.o server.o  -o semsrv
cc -g -W -Wall   -c -o client.o client.c
cc -g -W -Wall   -c -o semsrvUser.o semsrvUser.c
cc -g -W -Wall  client.o semsrvUser.o  -o semtest
$ ./semsrv &
$ ./semtest create foo
Created foo: 4099
$ ./semtest signal foo
Signalled foo
$ ./semtest wait foo
Waiting for foo...done
$ ./semtest get foo
Got foo: 4099
$ ./semtest create foo
Got foo (already exists): 4099
$ ./semtest destroy foo
Destroyed foo

Obviously to test more interesting behaviour, you’ll want more than one Terminal window…

The code is in this archive file.

Trackbacks

TrackBack URL for this entry:
http://alastairs-place.net/movabletype/mt-tb.cgi/274

Comments

The libdispatch semaphores also support timeouts.

I'm not sold on using Mach though, given the ambiguity of whether it's supported API or unsupported SPI, at least when I've asked.

It shouldn't be too hard to put an IPC semaphore together using existing IPC methods though, I might add that to my list of things to put together when I have time ^_^

The trouble with libdispatch, of course, is that you can only use it on 10.6; that isn’t an option for everyone. Plus dispatch semaphores (with timeout) don’t work cross-process, only in-process. Mach semaphores do, but you need a nameserver for them, which is what this code does.

You’re quite right that you could use other IPC methods to implement a semaphore with timeouts. Somewhat more complicated, relatively easy to get wrong and also I’m not sure how efficient it’d be in the case where it blocked (clearly you can make it very efficient for the non-blocking case by using shared memory).

A better approach in the long run might be just to submit some patches for xnu that add the necessary functions to the POSIX semaphore API.

As for the to-use-Mach or not-to-use-Mach question, I agree that Apple puts out very contradictory messages on the subject, and does contrary things like documenting NSMachPort and the Mach port related features of launchd while simultaneously saying that use of Mach APIs is discouraged.

I tend to take their “advice” as meaning that one shouldn’t use Mach APIs where there is a sensible, straightforward alternative, and that if one is using Mach APIs for something, there is a chance that Apple might break it in an update to OS X. In this case, I think it unlikely that the risk is significant.

“It’s possible that you might be interested in re-writing the server to use launchd”

On 10.6 there is a new ServiceManagement framework which provide functions to managed launchd services.
One interesting function is the SMJobBless() function. It provide a clean way to install a launchd plist.

«This function obviates the need for a setuid helper invoked via AuthorizationExecuteWithPrivileges() in order to install a launchd plist.»

One drawback is that it is not suitable for services shared across many applications (the daemon must be in Contents/Library/...) .

An other interesting function is the SMJobSubmit function which provide a way to dynamically register a service. So, in your sample, you can test if the semaphore server is registered, and if not, register it (or always try to register it and ignore failure). All other applications that need it will then use the first registered version.

Presumably SMJobSubmit() resolves to pretty similar code to what’s presently in there? Currently the program uses bootstrap_checkin() for that purpose.

I didn’t want this to depend on 10.6, because that isn’t an option for paid-for apps in many cases (yet), but the ServiceManagement framework is certainly an interesting option.

I've stuck this, with quite a few adaptation changes, into a fairly complex project that uses semaphores for various synchronization problems and it seems to be working with good performance.

Worth noting that cross-process semaphores do exist (A couple), and timeoutable semaphores do exist (numerous), but the combination does not. Also, this client code doesn't make use of semaphore_timedwait, leaving that as an exercise for the reader, but it is the only reason to implement this solution.

For this project, this solution was an absolute last resort (it borders on undocumented, it uses RPC, it adds quite a lot of complexity to the code and a makefile, and it adds possible installation issues for our project), but it's absolutely the only approach that has worked.

Post a comment

If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thank-you for your patience.

(Your e-mail address will not be displayed or included in any pages served on this site; nor will you get any spam as a result.)

A live preview of your comment will be displayed below. It should refresh automatically when you stop typing, but if not then the “Preview” button above will update it.

Live Comment Preview