2010-02-09 6 views
20

Ich muss Perl-Skript von Cron regelmäßig (~ alle 3-5 Minuten) ausführen. Ich möchte sicherstellen, dass nur eine Perl-Skript-Instanz in einer Zeit ausgeführt wird, so dass der nächste Zyklus erst beginnt, wenn der vorherige abgeschlossen ist. Könnte/sollte das durch eine eingebaute Funktionalität von Cron oder Perl erreicht werden oder muss ich es auf Skript-Ebene handhaben?Ausführen nur einer Perl-Skript-Instanz von Cron

Ich bin ziemlich neu zu Perl und Cron, so Hilfe und allgemeine Empfehlungen werden geschätzt.

+0

Welche Plattform reden Sie? * Nix? – weismat

+0

Es läuft auf Linux –

+0

http://blog.booking.com/highlander-daemons-without-daemons.html – stu42j

Antwort

14

Ich hatte immer Glück mit File::NFSLock, um eine exklusive Sperre für das Skript selbst zu erhalten. Diese

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; 

ist eine Art des gleiche wie die anderen Sperrdatei Vorschläge, außer ich tue nichts außer Versuch, das Schloss zu erhalten.

+0

Funktioniert das, wenn das Skript keine Schreibberechtigung hat? – mob

+0

Danke! Ich mag es am meisten, da es sehr kompakt ist und nichts zur Ablage schreibt. –

+1

Es verwendet immer noch eine Sperrdatei. Wenn das Skript foo.pl ist, verwendet es standardmäßig foo.pl.NFSLock. Sie benötigen also Schreibrechte für das Verzeichnis. – oylenshpeegul

1

AFAIK perl hat keine solche Sache buildin. Sie können einfach eine temporäre Datei erstellen, wenn Sie Ihre Anwendung starten und löschen, wenn Ihr Skript fertig ist.

+2

Sorry, ich sehe große Probleme voraus, wenn etwas schief geht (Neustart der Maschine) und die Datei nicht gelöscht wird ... –

+0

Fair zu sagen, aber wenn der Computer neu gestartet wird, während das Skript ausgeführt wird, werden Sie wahrscheinlich trotzdem Probleme haben (es sei denn, das Skript macht etwas extrem Triviales). –

+0

Neustart kann während der Ausführung des Skripts auftreten. Das Skript verwendet db-Transaktionen, also nehme ich an, dass es "überleben" wird und die Datenintegrität erhalten bleibt. –

-1

Angesichts der Häufigkeit würde ich normalerweise einen Daemon (Server) schreiben, der hübsch zwischen Jobläufen wartet (d. H. sleep()) anstatt zu versuchen, Cron für ziemlich feinkörnigen Zugriff zu verwenden.

Falls notwendig, auf Unix/Linux-Systemen können Sie es von /etc/inittab (oder Ersatz) ausführen, um sicherzustellen, dass es immer ausgeführt wird und automatisch neu gestartet wird, wenn der Prozess beendet wird oder stirbt.

Added: (und einige irrelevant Sachen entfernt)

Die immer vorhanden (Laufen, aber vor allem im Leerlauf) Daemon Ansatz den Vorteil der Beseitigung der Möglichkeit der gleichzeitigen Instanzen des Skripts durch cron wird gestartet hat automatisch .

Es bedeutet jedoch, dass Sie für die korrekte Verwaltung der Zeit verantwortlich sind, z. B. im Fall einer Überlappung (d. H. Ein vorheriger Lauf läuft noch, während ein neuer Auslöser auftritt). Dies kann Ihnen bei der Entscheidung helfen, ob Sie einen Forking-Daemon oder ein Design ohne Forking verwenden möchten. Threads bieten in diesem Szenario keinen Vorteil, so dass ihre Verwendung nicht berücksichtigt werden muss.

Dies beseitigt nicht die Möglichkeit, mehrere Prozesse laufen zu lassen, aber das ist ein häufiges Problem mit vielen Daemons. Die typische Lösung besteht in der Verwendung eines Semaphors, z. B. einer gegenseitig ausschließenden Sperre für eine Datei, um zu verhindern, dass eine zweite Instanz ausgeführt wird. Die Dateisperre wird automatisch vergessen, wenn der Prozess endet, so dass im Falle einer abnormalen Beendigung (z. B. Stromausfall) keine Säuberung der Sperre selbst notwendig ist.

Ein Ansatz unter Verwendung von Fcntl Modul und unter Verwendung eines Perl sysopen mit einer O_EXCL Flag (oder O_RDWR | O_CREAT | O_EXCL) war given by Greg Bacon. Die einzigen Unterschiede, die ich machen würde, sind das exklusive Sperren in den sysopen-Aufruf (d. H. Die von mir vorgeschlagenen Flags) und das Entfernen des redundanten flock-Aufrufs. Oh, und ich würde die UNIX (& Linux FHS) Dateisystem-und Namenskonventionen von /var/run/daemonname.pid folgen.

Ein anderer Ansatz wäre, djb's daemontools oder similar zu verwenden, um die Aufgabe zu "dämonisieren".

+0

Das Ausführen eines Daemon beseitigt nicht das Problem mit mehreren laufenden Skripten. Es ist leicht für jemanden oder etwas, versehentlich einen zweiten Daemon zu starten. Sie benötigen weiterhin eine PID-Datei, um dieses Problem zu beheben. – mpounsett

3

Ein typischer Ansatz besteht darin, dass jeder Prozess eine bestimmte Datei öffnet und sperrt. Dann liest der Prozess die in der Datei enthaltene Prozess-ID.

Wenn ein Prozess mit dieser ID ausgeführt wird, wird der Nachzügler ruhig beendet. Andernfalls schreibt der neue Gewinner seine Prozess-ID ($$ in Perl) in die PID-Datei, schließt den Handle (der die Sperre freigibt) und geht seiner Sache nach.

Beispiel Implementierung unter:

#! /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; 
11

Verwenden File::Pid der Skripts pid in einer Datei zu speichern, die das Skript für zu Beginn überprüfen sollte, und abzubrechen, falls vorhanden. Sie können die PID-Datei entfernen, wenn das Skript fertig ist, aber es ist nicht wirklich notwendig, da Sie später einfach überprüfen können, ob diese Prozess-ID noch aktiv ist (auch wenn Ihr Skript unerwartet abbricht):

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; 
+0

Ich war überrascht, keine Herde Anrufe in seiner Quelle zu finden! –

+0

@gbacon: Fühlen Sie sich frei, einen Patch einzureichen :) – Ether

+0

Ich würde diese Antwort nicht empfehlen. Sie haben eine Race Condition (Aufruf nach dem Laufen). Es wird wahrscheinlich nie ein Problem in einem Cron-Job sein, aber besser vermieden werden, da es bessere Antworten gibt (siehe akzeptiert). – Calimo

11

Das Sys :: RunAlone-Modul macht das, was Sie wollen, sehr schön. Fügen Sie einfach

use Sys::RunAlone; 

in der Nähe der Spitze Ihres Codes.

+0

+1 danach in den DANK Lesen zu installieren: (Dank) Booking.com für die Verwendung dieser stark in der Produktion und mir erlaubt, dieses Modul zu verbessern. – moodboom

1

Ich habe das immer benutzt - klein und einfach - keine Abhängigkeit von irgendeinem Modul und funktioniert sowohl Windows + Linux.

use Fcntl ':flock';      

### Check to make sure there is only one instance ### 
open SELF, "< $0" or die("Cannot run two instances of this program"); 
unless (flock SELF, LOCK_EX | LOCK_NB) { 
    print "You cannot run two instances of this program , a process is still running"; 
    exit 1; 
}