Locking a single process

[ Perl tips index ]
[ Subscribe to Perl tips ]

Imagine the situation where we have a faxmodem connected to our machine, and a program that sends a fax to its destination. In this situation we only want a single instance of the program running at any time, since the faxmodem can only be sending a single fax at a time.

The problem of ensuring that only a single process is running is a common one. Today we'll investigate a simple solution to solve the problem in Perl.

A brief look at locking

Perl comes with a portable locking mechanism called flock, which is short for file-lock. Using flock we can apply advisory locks to any filehandle:

        use Fcntl qw(:flock);
        flock(FILE1, LOCK_EX) or die "Cannot lock - $!";  # Exclusive lock
        flock(FILE2, LOCK_SH) or die "Cannot lock - $!";  # Shared lock

The use Fcntl qw(:flock) line simply imports the LOCK_EX, LOCK_SH and a few other constants for our use. Once we've done that, we can ask for either exclusive or shared locks on our filehandles.

Perl's flock mechanism can be used to lock any filehandle, including sockets and streams like STDIN. If the lock fails, or your operating system does not support locking on the requested filehandle, flock will return false.

By default, flock will wait indefinitely until a lock is obtained, however we can request a lock be made in a non-blocking fashion by using the special constant LOCK_NB:

        use Fcntl qw(:flock);
        flock(FILE, LOCK_EX|LOCK_NB) or die "Cannot lock - $!";

While Perl allows us to unlock files by using the LOCK_UN operation, this happens automatically when a file is closed. Closing a file is the preferred way to unlock it, as it ensures that not only locks and resources are released, but also that we cannot accidently access or write to the file after it has been unlocked.

Locking a program

It's common to see external lockfiles being used to ensure that only a single instance of a program is running on a machine. This has the additional overhead of creating and tidying up the lockfile. Luckily for us, this is rarely needed in Perl.

We can take advantage of the fact that our program's source code will be stored in a file, and that file must be accessible to the Perl interpreter in order for it to run. Rather than locking an external file, we can simply lock our own source code, the filename of which can be found in the special variable $0.

        use Fcntl qw(:flock);
        open(SELF,"<",$0) or die "Cannot open $0 - $!";
        flock(SELF, LOCK_EX|LOCK_NB) or die "Already running.";

Perl programs also allow data to be stored at the end of their source code, a special __DATA__ section. If this exists, the data is accessible through a special filehandle called DATA. We can use this as an alternative method to lock our own program.

        use Fcntl qw(:flock);
        flock(DATA, LOCK_EX|LOCK_NB) or die "Already running.";
        # ...
        __DATA__

This achieves the same effect as the previous example without having to open the program file. One major drawback is that the __DATA__ section must appear at the bottom of your code and is therefore often a long way away from your locking code. This distance is a potential source of bugs. Should the __DATA__ section be accidently deleted, or should you forget to add it, the flock will fail and the program will terminate with your die message. In this case, we'd be told that the program was already running, not that __DATA__ didn't exist to be locked!

More on locking

For more coverage on using flock, the slides from Mark Jason Dominus' File Locking Tricks and Traps make excellent reading. They can be found at http://perl.plover.com/yak/flock/ .

[ Perl tips index ]
[ Subscribe to Perl tips ]


This Perl tip and associated text is copyright Perl Training Australia. You may freely distribute this text so long as it is distributed in full with this Copyright noticed attached.

If you have any questions please don't hesitate to contact us:

Email: contact@perltraining.com.au
Phone: 03 9354 6001 (Australia)
International: +61 3 9354 6001

Valid XHTML 1.0 Valid CSS