views:

72

answers:

4

I need to ensure that only one copy of my Perl script is running at a time. According to the suggestions here I wrote a sub to do the check:

sub check_instances {
    open my $fh, '<', $0 or die $!; 

    unless (flock($fh, LOCK_EX|LOCK_NB)) {
        print "$0 is already running. Exiting.\n";
        exit 1;
    } 
}

But it doesn't work. What can be the issue?

+1  A: 

File locking can fail for various reasons (for example if the file is on a network file system like NFS).

My solution is to create a directory while the script runs. Creating directories is always an atomic operation.

Aaron Digulla
+6  A: 

You're using a lexical filehandle scoped inside the sub. When check_instances returns, the filehandle is automatically closed, which releases the lock. So you'll never see a conflict unless two copies check at exactly the same time.

Ensure that the filehandle remains open as long as the script is running (or as long as you want to maintain the lock). For example:

{
my $fh;
sub check_instances {
    return if $fh; # We already checked
    open $fh, '<', $0 or die $!; 

    unless (flock($fh, LOCK_EX|LOCK_NB)) {
        print "$0 is already running. Exiting.\n";
        exit 1;
    } 
}
} # end scope of $fh

This would also be a good place to use a state variable, if you can require Perl 5.10.

cjm
Of course, one can also use a global file handler: `open LOCK, '<', $0 or die $!` and not care about the scope.
eugene y
+2  A: 

You can check the process list for other instances (Proc::ProcessTable can help), but a common route taken by unix programs in many languages is to create a pid file -- see File::Pid.

Ether
+2  A: 

The normal semantics of flock may require you to open the filehandle in write mode, say,

open $fh, '>>', $0;
open $fh, '+<', $0;

(From perldoc -f flock)

Note that the fcntl(2) emulation of flock(3) requires that FILEHANDLE be open with read intent to use LOCK_SH and requires that it be open with write intent to use LOCK_EX.

mobrule