it-swarm.com.de

Verhindern Sie, dass doppelte Cron-Jobs ausgeführt werden

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 übereinander "gestapelt" werden. Ich denke, dies ist ein Parallelitätsproblem - d. H. Die Skriptausführung muss sich gegenseitig ausschließen.

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

97
Tom

Es gibt eine Reihe von Programmen, die diese Funktion automatisieren, den Ärger und mögliche Fehler davon abhalten, dies selbst zu tun, und das Problem der veralteten Sperre vermeiden, indem Sie auch hinter den Kulissen eine Herde verwenden (was ein Risiko darstellt, wenn Sie nur Berührungen verwenden). . Ich habe in der Vergangenheit lockrun und lckdo 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
124
womble

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

(
  flock -x -w 5 99
  ## Do your stuff here
) 99>/path/to/my.lock
29
Philip Reynolds

Tatsächlich, flock -n kann anstelle von lckdo * verwendet werden, sodass Sie Code von Kernel-Entwicklern verwenden.

Aufbauend auf Wombles Beispiel würden Sie etwas schreiben wie:

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

Übrigens, wenn Sie sich den Code ansehen, tun alle flock, lockrun und lckdo genau dasselbe, so dass es nur eine Frage ist, die Ihnen am leichtesten zur Verfügung steht.

23
Amir

Sie haben nicht angegeben, ob das Skript auf den Abschluss des vorherigen Laufs warten soll oder nicht. Mit "Ich möchte nicht, dass die Jobs" übereinander gestapelt "werden" implizieren Sie vermutlich, 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

Sie können eine Sperrdatei verwenden. Erstellen Sie diese Datei, wenn das Skript gestartet wird, und löschen Sie sie, wenn es beendet ist. Das Skript sollte vor dem Ausführen seiner Hauptroutine prüfen, ob die Sperrdatei vorhanden ist, und entsprechend vorgehen.

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

2
Born To Ride

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

EIN systemd.timer

Im /etc/systemd/system/myjob.service oder ~/.config/systemd/user/myjob.service:

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

Im /etc/systemd/system/myjob.timer oder ~/.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 eine andere Instanz des Service nicht 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
1
Amir

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 sie möglicherweise deaktivieren und zu einem Programm im Daemon-Stil machen.

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

1
dubiousjim

Ich würde empfehlen, den Befehl run-one zu verwenden - viel einfacher als den Umgang mit den Sperren. Aus den Dokumenten:

run-one ist ein Wrapper-Skript, das nicht mehr als eine eindeutige Instanz eines Befehls mit einer eindeutigen Reihe von Argumenten ausführt. Dies ist häufig bei Cronjobs hilfreich, wenn nicht mehr als eine Kopie gleichzeitig ausgeführt werden soll.

run-this-one ist genau wie run-one, außer dass pgrep und kill verwendet werden, um laufende Prozesse zu finden und zu beenden, die dem Benutzer und gehören Übereinstimmung der Zielbefehle und Argumente. Beachten Sie, dass run-this-one blockiert, während versucht wird, übereinstimmende Prozesse abzubrechen, bis alle übereinstimmenden Prozesse beendet sind.

run-one-konstant funktioniert genau wie run-one, außer dass "COMMAND [ARGS]" bei jedem Beenden von COMMAND (Null oder Nicht-Null) erneut angezeigt wird. .

keep-one-running ist ein Alias ​​für run-one-konstant.

run-one-till-success funktioniert genau wie run-one-konstant, außer dass "COMMAND [ARGS]" erneut angezeigt wird, bis COMMAND erfolgreich beendet wird (dh verlässt Null).

run-one-till-fail funktioniert genau wie run-one-konstant, außer dass "COMMAND [ARGS]" erneut angezeigt wird, bis COMMAND mit einem Fehler beendet wird (dh , beendet ungleich Null).

1
Yurik

Ich habe ein JAR erstellt, um ein solches Problem zu lösen, bei dem doppelte Cron ausgeführt werden. Dies könnte Java oder Shell Cron sein. Übergeben Sie einfach den Cron-Namen in Duplicates.CloseSessions ("Demo.jar"). Dies wird suchen und töten Existierende PID für dieses Cron außer aktuell. Ich habe eine Methode implementiert, um dies zu tun. String proname = ManagementFactory.getRuntimeMXBean (). getName (); String pid = proname.split ("@") [0]; System.out.println ("Aktuelle 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 töte killid string mit erneutem Shell-Befehl

0
Sachin Patil

Die Antwort von @Philip Reynolds beginnt ohnehin nach Ablauf der Wartezeit von 5 Sekunden mit der Ausführung des Codes, ohne die Sperre zu erhalten. Folgende Flock scheint nicht zu funktionieren Ich habe die Antwort von @Philip Reynolds auf geändert

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

so dass der Code niemals gleichzeitig ausgeführt werden würde. Stattdessen wird der Prozess nach 5 Sekunden Wartezeit mit 1 beendet, wenn die Sperre bis dahin nicht erreicht wurde.

0
user__42