Run only one instance of a program at a time

Recently I wanted to schedule a Perl app to run every minute on a server, but if an instance of the app was already running, it should exit and do nothing. This is a common problem and I was able to solve it with a lockfile. Let’s see how to use lockfiles in Perl.

Lockfiles you say?

Most operating systems support file locking - it’s an essential tool to prevent multiple processes updating a file at the same time and causing data loss. Processes obtain file locks when they are accessing a file to prevent other processes changing them, and release the file lock when they’re done, freeing the file to be used by other processes again.

Programs can use the lock file principle to prevent multiple instances of themselves running at the same time. When the program starts it tries to lock the lockfile, if successful it executes the program, else it exits. When the program process ends, any locks it obtained are removed by the OS. You may have seen lockfiles before, they are usually ordinary files with a .lock extension.

File locking in Perl

Perl provides the flock function for file locking. It takes a filehandle and a constant value for the lock type. So to get an exclusive lock on a file, I could do:

open my $file, ">", "app.lock" or die $!; 
flock $file, 2 or die "Unable to lock file $!";
# we have the lock

This code starts by opening a write filehandle to the file app.lock. If successful, it attempts to get an exclusive lock on the file by calling flock with the number 2. An exclusive lock means no other process can access the file whilst the lock is active. Remembering the constant values for lock types can be a pain, so helpfully the Fcntl module will export constant names if you ask nicely. I’ll update the code to do that:

use Fcntl qw(:flock);
open my $file, ">", "app.lock" or die $!; 
flock $file, LOCK_EX or die "Unable to lock file $!";
# we have the lock

This code does the same thing as before but we don’t need to remember the constant value for the lock type (LOCK_EX == exclusive lock). Note there is no need to unlock the file - when the program exits, the lock will be removed automatically.

Non-blocking flock

So far so good but we have a problem. If the file is locked, flock will block and keep our program waiting around until the lock is removed. I want is the program to exit immediately if it can’t obtain the lock. The only way to check if a file is locked is with flock though! Fortunately the flock developers had considered this issue, and I can pass an extra argument to indicate I want a non-blocking lock.

use Fcntl qw(:flock);
open my $file, ">", "app.lock" or die $!; 
flock $file, LOCK_EX|LOCK_NB or die "Unable to lock file $!";
# we have the lock

I’ve added |LOCK_NB to the flock arguments and now it will return false immediately if it cannot obtain an exclusive lock.This provides the non-blocking behavior I need.

Testing it out

I’m going to put this locking code into a quick script so I can test the lock functionality:

#!/usr/bin/env perl
use Fcntl qw(:flock);
open my $file, ">", "app.lock" or die $!; 
flock $file, LOCK_EX|LOCK_NB or die "Unable to lock file: $!";

sleep(60);

I’ll save the script as sleep60.pl and test it at the terminal:

$ chmod 700 sleep60.pl
$ ./sleep60.pl&
[2] 21505
$ ./sleep60.pl
Unable to lock file Resource temporarily unavailable at ./sleep60.pl line 4.

Looking good! I tried to run the script twice and the second time, the system printed the expected error message and exited.

Avoiding external files

Using an external file feels kind-of-dirty. What I’d really like to do is tidy up by deleting the lockfile once the program has finished. However unlocking and deleting the file involves extra steps which may introduce a race condition. Instead of deleting the file, what if we never created it? One way to do this is to use the __DATA__ filehandle, like so:

#!/usr/bin/env perl
use Fcntl qw(:flock);
flock DATA, LOCK_EX|LOCK_NB or die "Unable to lock file $!";

sleep(60);
__DATA__

This version of the script opens a lock on the DATA filehandle and creates no external files. Mark Jason Dominus showed this ingenious trick years ago. Another trick Mark showed was to open the lockfile on the program file itself:

#!/usr/bin/env perl
use Fcntl qw(:flock);
open our $file, '<', $0 or die $!;
flock $file, LOCK_EX|LOCK_NB or die "Unable to lock file $!";

sleep(60);

This frees up DATA and has the added benefit that the code can be exported by a module (by using our instead of my). Note that the open arguments have been changed to use a read-only filehandle to avoid truncating the source code of the program! If you need this behavior, you can implement it yourself as shown above, or use my module IPC::Lockfile, which will do it for you. If you need more refined lockfile functionality, have a look at Sys::RunAlone which uses the same trick (thanks to BooK for the reference). There are also plenty of other options on CPAN.

Update: added Sys::RunAlone reference - 2015-11-28.

Tags

David Farrell

David is the founder and editor of PerlTricks.com. An organizer of the New York Perl Meetup, he works for ZipRecruiter as a software developer.

Browse their articles