Running only one Perl script instance by cron

Viewed 14475

I need to run Perl script by cron periodically (~every 3-5 minutes). I want to ensure that only one Perl script instance will be running in a time, so next cycle won't start until the previous one is finished. Could/Should that be achieved by some built-in functionality of cron, Perl or I need to handle it at script level?

I am quite new to Perl and cron, so help and general recommendations are appreciated.

7 Answers

I have always had good luck using File::NFSLock to get an exclusive lock on the script itself.

use Fcntl qw(LOCK_EX LOCK_NB);
use File::NFSLock;

# Try to get an exclusive lock on myself.
my $lock = File::NFSLock->new($0, LOCK_EX|LOCK_NB);
die "$0 is already running!\n" unless $lock;

This is sort of the same as the other lock file suggestions, except I don't have to do anything except attempt to get the lock.

Use File::Pid to store the script's pid in a file, which the script should check for at the start, and abort if found. You can remove the pidfile when the script is done, but it's not truly necessary, as you can simply check later to see if that process id is still alive (which will also account for the cases when your script aborts unexpectedly):

use strict;
use warnings;
use File::Pid;

my $pidfile = File::Pid->new({file => /var/run/myscript});
exit if $pidfile->running();

$pidfile->write();

# ... rest of script...

# end of script
$pidfile->remove();
exit;

A typical approach is for each process to open and lock a certain file. Then the process reads the process ID contained in the file.

If a process with that ID is running, the latecomer exits quietly. Otherwise, the new winner writes its process ID ($$ in Perl) to the pidfile, closes the handle (which releases the lock), and goes about its business.

Example implementation below:

#! /usr/bin/perl

use warnings;
use strict;

use Fcntl qw/ :DEFAULT :flock :seek /;

my $PIDFILE = "/tmp/my-program.pid";
sub take_lock {
  sysopen my $fh, $PIDFILE, O_RDWR | O_CREAT or die "$0: open $PIDFILE: $!";
  flock $fh => LOCK_EX                       or die "$0: flock $PIDFILE: $!";

  my $pid = <$fh>;
  if (defined $pid) {
    chomp $pid;
    if (kill 0 => $pid) {
      close $fh;
      exit 1;
    }
  }
  else {
    die "$0: readline $PIDFILE: $!" if $!;
  }

  sysseek  $fh, 0, SEEK_SET or die "$0: sysseek $PIDFILE: $!";
  truncate $fh, 0           or die "$0: truncate $PIDFILE: $!";
  print    $fh "$$\n"       or die "$0: print $PIDFILE: $!";
  close    $fh              or die "$0: close: $!";
}

take_lock;
print "$0: [$$] running...\n";
sleep 2;

AFAIK perl has no such thing builtin. You could easily create a temporary file, when you start your application and delete it, when your script is done.

Given the frequency I would normally write a daemon (server) that nicely waits idly between job runs (i.e. sleep()) rather than try to use cron for fairly fine-grained access.

If necessary, on Unix / Linux systems you could run it from /etc/inittab (or replacement) to ensure that it always running, and is automatically restarted in the process is killed or dies.

Added: (and some irrelevant stuff removed)

The always present (running, but mostly idle) daemon approach has the benefit of eliminating the possibility of concurrent instances of the script being being started by cron automatically.

However it does mean you are responsible for managing the timing correctly, such as in the case of there is an overlap (i.e. a previous run is still running, while a new trigger occurs). This may help you decide whether to use a forking daemon or non-forking design. Threads don't provide any advantage in this scenario, so there is no need to consider their usage.

This does not completely eliminate the possibility of multiple processes running, but that a common problem with many daemons. The typical solution is to use a semaphore such as a mutually-exclusive lock on a file, to prevent a second instance from being run. The file-lock is automatically forgotten when the process ends, so in the case of abnormal termination (e.g. power failure) there is no clean-up necessary of the lock itself.

An approach using Fcntl module, and using a Perl sysopen with a O_EXCL flag (or O_RDWR | O_CREAT | O_EXCL) was given by Greg Bacon. The only differences I would make are combine exclusive locking into the sysopen call (i.e. use the flags I've suggested), and remove the then redundant flock call. Oh, and I would follow the UNIX (& Linux FHS) file-system and naming conventions of /var/run/daemonname.pid.

Another approach would be to use djb's daemontools or similar to "daemonize" the task.

Related