Verhindern, dass doppelte Cron-Jobs ausgeführt werden

92

Ich habe geplant, dass ein Cron-Job jede Minute ausgeführt wird, aber manchmal dauert es mehr als eine Minute, bis das Skript fertig ist, und ich möchte nicht, dass die Jobs sich gegenseitig überlagern. Ich denke, dies ist ein Nebenläufigkeitsproblem - dh die Skriptausführung muss sich gegenseitig ausschließen.

Um das Problem zu lösen, habe ich das Skript veranlasst, nach einer bestimmten Datei (" lockfile.txt ") zu suchen und zu beenden, wenn sie existiert oder touchnicht. Aber das ist ein ziemlich mieses Semaphor! Gibt es eine bewährte Methode, die ich kennen sollte? Sollte ich stattdessen einen Daemon geschrieben haben?

Tom
quelle

Antworten:

118

Es gibt einige Programme, die diese Funktion automatisieren, Ärger und potenzielle Fehler vermeiden und das Problem der veralteten Sperre vermeiden, indem Sie auch im Hintergrund eine Herde verwenden (was ein Risiko darstellt, wenn Sie nur Touch verwenden). . Ich habe lockrunund lckdoin der Vergangenheit verwendet, aber jetzt gibt es flock(1) (in neueren Versionen von Util-Linux), was großartig ist. Es ist wirklich einfach zu bedienen:

* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job
womble
quelle
2
lckdo wird aus moreutils entfernt, da sich flock (1) nun in util-linux befindet. Und dieses Paket ist in Linux-Systemen grundsätzlich obligatorisch, sodass Sie sich auf seine Präsenz verlassen können sollten. Informationen zur Verwendung finden Sie weiter unten.
Jldugger
Ja, Herde ist jetzt meine bevorzugte Option. Ich werde sogar meine Antwort entsprechend aktualisieren.
womble
Kennt jemand den Unterschied zwischen flock -n file commandund flock -n file -c command?
Nanne
2
@Nanne, ich müsste den Code überprüfen, um sicherzugehen, aber meine Vermutung ist, dass -cder angegebene Befehl über eine Shell ausgeführt wird (wie in der Manpage angegeben), während die "nackte" (nicht -c) Form nur execder angegebene Befehl ist . Wenn Sie etwas in die Shell einfügen, können Sie Shell-ähnliche Aktionen ausführen (z. B. mehrere mit ;oder getrennte Befehle ausführen &&). Sie können jedoch auch Shell-Erweiterungsangriffe ausführen , wenn Sie nicht vertrauenswürdige Eingaben verwenden.
womble
1
Es war ein Argument für den (hypothetischen) frequent_cron_jobBefehl, der zu zeigen versuchte, dass er jede Minute ausgeführt wurde. Ich habe es entfernt, da es nichts Nützliches hinzufügt und Verwirrung stiftet (deins, wenn über die Jahre niemand anderes mehr da ist).
womble
28

Der beste Weg in der Schale ist die Verwendung von Herde (1)

(
  flock -x -w 5 99
  ## Do your stuff here
) 99>/path/to/my.lock
Philip Reynolds
quelle
1
Ich kann eine knifflige Verwendung der fd-Umleitung nicht empfehlen. Es ist einfach zu großartig.
womble
1
Parset nicht für mich in Bash oder ZSH, muss das Leerzeichen beseitigen 99und >so ist es99> /...
Kyle Brandt
2
@Javier: Das heißt nicht, dass es nicht trickreich und arkan ist, nur, dass es dokumentiert , trickreich und arkan ist.
womble
1
Was würde passieren, wenn Sie neu starten, während dies ausgeführt wird, oder wenn der Prozess irgendwie abgebrochen wird? Wäre es dann für immer gesperrt?
Alex R
5
Ich verstehe, dass diese Struktur eine exklusive Sperre erzeugt, aber ich verstehe nicht die Mechanik, wie dies erreicht wird. Was ist die Funktion der '99' in dieser Antwort? Möchte das bitte jemand erklären? Vielen Dank!
Asciiom
22

Tatsächlich flock -nkann anstelle von lckdo* auch Code von Kernel-Entwicklern verwendet werden.

Aufbauend auf dem Beispiel von womble würden Sie etwa schreiben:

* * * * * flock -n /some/lockfile command_to_run_every_minute

BTW, Blick auf den Code, die alle flock, lockrunund lckdotun genau dasselbe, es ist also nur eine Frage von denen am ehesten für Sie verfügbar ist.

Amir
quelle
2

Sie können eine Sperrdatei verwenden. Erstellen Sie diese Datei, wenn das Skript gestartet wird, und löschen Sie sie, wenn sie beendet ist. Bevor das Skript seine Hauptroutine ausführt, sollte es prüfen, ob die Sperrdatei vorhanden ist, und entsprechend vorgehen.

Sperrdateien werden von Initscripts und vielen anderen Anwendungen und Dienstprogrammen in Unix-Systemen verwendet.

Geboren um zu reiten
quelle
1
Dies ist der einzige Weg, wie ich ihn jemals persönlich gesehen habe. Ich verwende auf Vorschlag des Betreuers als Spiegel für ein OSS-Projekt
warren
2

Sie haben nicht angegeben, ob das Skript auf den Abschluss der vorherigen Ausführung warten soll oder nicht. Mit "Ich möchte nicht, dass die Jobs sich" übereinander "stapeln", gehen Sie vermutlich davon aus, dass das Skript beendet werden soll, wenn es bereits ausgeführt wird.

Wenn Sie sich also nicht auf lckdo oder ähnliches verlassen möchten, können Sie dies tun:


PIDFILE=/tmp/`basename $0`.pid

if [ -f $PIDFILE ]; then
  if ps -p `cat $PIDFILE` > /dev/null 2>&1; then
      echo "$0 already running!"
      exit
  fi
fi
echo $$ > $PIDFILE

trap 'rm -f "$PIDFILE" >/dev/null 2>&1' EXIT HUP KILL INT QUIT TERM

# do the work

Aleksandar Ivanisevic
quelle
Vielen Dank, Ihr Beispiel ist hilfreich. Ich möchte, dass das Skript beendet wird, wenn es bereits ausgeführt wird. Danke, dass du ickdo erwähnt hast - es scheint den Trick zu tun.
Tom
FWIW: Ich mag diese Lösung, weil sie in ein Skript eingebunden werden kann, sodass das Sperren unabhängig davon funktioniert, wie das Skript aufgerufen wird.
David G
1

Dies könnte auch ein Zeichen dafür sein, dass Sie das Falsche tun. Wenn Ihre Jobs so eng und so häufig ausgeführt werden, sollten Sie möglicherweise in Betracht ziehen, sie zu entfernen und sie zu einem Programm im Dämon-Stil zu machen.


quelle
3
Ich stimme dem von Herzen nicht zu. Wenn Sie etwas haben, das regelmäßig ausgeführt werden muss, ist es eine Lösung, es zu einem Daemon zu machen. Die Verwendung einer Sperrdatei zur Verhinderung von Unfällen ist eine vernünftige Lösung, bei deren Verwendung ich noch nie ein Problem hatte.
womble
@womble Ich stimme zu; aber ich mag es, Nüsse mit Vorschlaghämmern zu zertrümmern! :-)
wzzrd
1

Ihr Cron-Daemon sollte keine Jobs aufrufen, wenn frühere Instanzen von ihnen noch ausgeführt werden. Ich bin der Entwickler von einem Cron-Daemon- Dcron , und wir versuchen dies ausdrücklich zu verhindern. Ich weiß nicht, wie Vixie Cron oder andere Dämonen damit umgehen.

zweifelhafter jim
quelle
1

Ich würde die Verwendung des Befehls run-one empfehlen - viel einfacher als das Behandeln der Sperren. Aus den Dokumenten:

run-one ist ein Wrapper-Skript, das nicht mehr als eine eindeutige Instanz eines Befehls mit einem eindeutigen Satz von Argumenten ausführt. Dies ist häufig bei Cronjobs hilfreich, wenn Sie nicht mehr als eine Kopie gleichzeitig ausführen möchten.

run-this-one ist genau wie run-one, außer dass es pgrep und kill verwendet, um alle laufenden Prozesse zu finden und zu beenden, die dem Benutzer gehören und mit den Zielbefehlen und -argumenten übereinstimmen. Beachten Sie, dass run-this-one blockiert, während versucht wird, übereinstimmende Prozesse abzubrechen, bis alle übereinstimmenden Prozesse beendet sind.

run-one-constant funktioniert genauso wie run-one, außer dass es "COMMAND [ARGS]" bei jedem Beenden von COMMAND erneut ausgibt (null oder nicht null).

Keep-One-Running ist ein Alias ​​für Run-One-Constant.

run-one-until-success funktioniert genauso wie run-one-constant, mit der Ausnahme, dass es "COMMAND [ARGS]" erneut ausgibt, bis COMMAND erfolgreich beendet wird (dh Null verlässt).

run-one-until-failure funktioniert genauso wie run-one-constant, mit der Ausnahme, dass es "COMMAND [ARGS]" erneut erzeugt, bis COMMAND mit einem Fehler beendet wird (dh nicht Null endet).

Yurik
quelle
1

Nachdem systemd jetzt nicht mehr verfügbar ist, gibt es auf Linux-Systemen einen weiteren Planungsmechanismus:

EIN systemd.timer

In /etc/systemd/system/myjob.serviceoder ~/.config/systemd/user/myjob.service:

[Service]
ExecStart=/usr/local/bin/myjob

In /etc/systemd/system/myjob.timeroder ~/.config/systemd/user/myjob.timer:

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target

Wenn die Serviceeinheit bereits aktiviert ist, wenn der Timer das nächste Mal aktiviert wird, wird keine weitere Instanz des Service gestartet.

Eine Alternative, die den Job einmal beim Booten und eine Minute nach jedem Lauf startet:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target
Amir
quelle
0

Ich habe ein Glas erstellt, um ein solches Problem zu lösen, da doppelte Cron ausgeführt werden, z. B. Java oder Shell Cron. Übergeben Sie einfach den Cron-Namen in Duplicates.CloseSessions ("Demo.jar"). Dadurch wird die vorhandene PID für diesen Cron mit Ausnahme der aktuellen gesucht und beendet. Ich habe eine Methode implementiert, um dieses Zeug zu machen. String proname = ManagementFactory.getRuntimeMXBean (). GetName (); String pid = proname.split ("@") [0]; System.out.println ("Current PID:" + pid);

            Process proc = Runtime.getRuntime().exec(new String[]{"bash","-c"," ps aux | grep "+cronname+" | awk '{print $2}' "});

            BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            String s = null;
            String killid="";

            while ((s = stdInput.readLine()) != null ) {                                        
                if(s.equals(pid)==false)
                {
                    killid=killid+s+" ";    
                }
            }

Und dann killid string mit dem Befehl again shell töten

Sachin Patil
quelle
Ich denke nicht, dass dies wirklich die Frage beantwortet.
Kasperd
0

@Philip Reynolds Antwort startet die Ausführung des Codes ohnehin nach Ablauf der Wartezeit von 5 Sekunden, ohne die Sperre zu erhalten. Im Anschluss an Flock scheint nicht zu funktionieren geändert ich @Philip Reynolds Antwort auf

(
  flock -w 5 -x 99 || exit 1
  ## Do your stuff here
) 99>/path/to/my.lock

damit der Code niemals gleichzeitig ausgeführt wird. Stattdessen wird der Prozess nach 5 Sekunden mit 1 beendet, wenn er bis dahin nicht die Sperre erhalten hat.

user__42
quelle