Process queuing using lock files

tags: programming · tutorials

Locking will be done using the fcntl(2) system call. I’m aware of lockf(3) and flock(2), but both of them normally use fcntl(2) under the hood, and they are not as portable. More information on file locking.

For a real use-case, I’ve written a notification program which uses the same mechanism, so that notifications can be queued without having to run a daemon, such as D-Bus, in the background.

First create the lock file with write permissions. O_CREAT is used to create the file in case it doesn’t exist already:

#include <err.h>
#include <fcntl.h>
...
char *lockfile = "/tmp/foo.lock";
int fd;

if ((fd = open(lockfile, O_CREAT | O_WRONLY, 0600)) < 0)
	err(1, "open(%s)", lockfile);

Locking commands operate on the flock structure. Before a call to fcntl(2) is made, we need to write the following fields:

struct flock {
	off_t	l_start;	/* starting offset */
	off_t	l_len;		/* len = 0 means until end of file */
	short	l_type;		/* lock type: read/write, etc. */
	short	l_whence;	/* type of l_start */
	...
};

The starting offset, l_len, can be anything, but 0 is what makes the most sense. We’ll set l_len to 0 as well, since we want each process to lock the entire file. The lock type, l_type, needs to be an exclusive lock (F_WRLCK), that is, a lock that prevents any other process from setting a lock on that area before it’s released. l_whence will be set to SEET_SET to indicate that the relative offset l_start will be measured from the beginning of the file:

struct flock fl;

fl.l_len = 0;
fl.l_start = 0;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;

The F_SETLKW command will make the calling process wait until the lock request can be satisfied. There’s also F_SETLK, but it returns immidiately if the lock is already acquired, which is not very useful for queuing processes:

if (fcntl(fd, F_SETLKW, &fl) < 0)
	err(1, "fcntl(F_SETLKW)");

When we get past the call to fcntl(2), it means that we have acquired the lock until a call to close(2) is made. Here is where we’ll put the part of the code we want to queue, in this case a simple printf followed by a 3-second sleep to make sure queuing really works:

printf("hello from %d\n", getpid());
sleep(3);

When done, release the lock:

close(fd);

To test the code, open two terminals and run the program on both of them. You’ll see the process that was run last will not execute the code after fcntl(2) until the first one has finished.