tags:

views:

735

answers:

6

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.

A: 

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

ZeissS
Sorry, I foresee big troubles if something goes wrong (machine restart) and file is not deleted...
Gennady Shumakher
Fair to say, but if the machine is restarted while your script is running, you'll probably face troubles anyway (unless the script does something extremely trivial).
RegDwight
Restart can happen during the script run. The script is using db transactions, so I suppose it'll "survive" restart preserving data integrity.
Gennady Shumakher
than write pid into that file and check if a process with that pid is still running.
ZeissS
+3  A: 

Please read this:

http://www.perlmonks.org/?node_id=590619

codaddict
This would be better as a comment rather than an answer, since you didn't actually answer the question.
Ether
A: 

How I've seen this normally handled is to let the cron job launch every 5 minutes, but with a random length sleep period (0-150 seconds, 0-2.5 minutes), in the start of the Perl script.

Of course I have seen this normally done with things like a anti-virus data file update process, on a slower basis, more like once a day rather than every 3-5 minutes.

Given the frequency I would normally write a daemon (server) that nicely waits between job runs (i.e. sleep()) rather than try to use cron for fairly fine-grained access. This approach has the benefit of eliminating the issue about multiple processes running. If necessary, on Unix / Linux systems you could run it from /etc/inittab to ensure that it always running, and is automatically restarted in the process is killed or dies.

mctylr
+3  A: 

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;
Greg Bacon
+2  A: 

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;
Ether
I was surprised to find no flock calls in its source!
Greg Bacon
@gbacon: feel free to submit a patch :)
Ether
+4  A: 

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.

oylenshpeegul
Does this work if the script doesn't have write permission?
mobrule
Thanks! I like it most, since it's very compact and not writing anything to file.
Gennady Shumakher
It's still using a lock file. If the script is foo.pl, then it uses foo.pl.NFSLock by default. So you need write permission to the directory.
oylenshpeegul