Generische Lösung, um zu verhindern, dass ein langer Cron-Job parallel ausgeführt wird?

27

Ich suche nach einer einfachen und generischen Lösung, mit der Sie jedes Skript oder jede Anwendung in crontab ausführen und verhindern können, dass es zweimal ausgeführt wird.

Die Lösung sollte unabhängig vom ausgeführten Befehl sein.

Ich gehe davon aus, dass es so aussehen sollte, als ob die lock && (command ; unlock)Sperre false zurückgeben würde, wenn es eine andere Sperre gäbe.

Der zweite Teil würde so aussehen, als hätte er den Befehl lock, run und unlock erhalten, nachdem der Befehl ausgeführt wurde, auch wenn er einen Fehler zurückgibt.

Sorin
quelle

Antworten:

33

Schauen Sie sich das run-one-Installiere run-one Paket an. Von der Manpage für den run-oneBefehlManpage-Symbol :

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.

Wie timeoder stellen sudoSie es einfach dem Befehl voran. Ein Cronjob könnte also so aussehen:

  */60 * * * *   run-one rsync -azP $HOME example.com:/srv/backup

Weitere Informationen und Hintergründe finden Sie im Blog-Beitrag von Dustin Kirkland.

undetwas
quelle
5

Ein sehr einfacher Weg, eine Sperre aufzubauen:

if mkdir /var/lock/mylock; then
  echo "Locking succeeded" >&2
else
  echo "Lock failed - exit" >&2
  exit 1
fi

Ein Skript, das ausgeführt werden soll, muss die Sperre erstellen. Wenn die Sperre vorhanden ist, ist ein anderes Skript ausgelastet, sodass das erste Skript nicht ausgeführt werden kann. Wenn die Datei nicht existiert, hat kein Skript die Sperre erhalten. Das aktuelle Skript erhält also die Sperre. Nach Beendigung des Skripts muss die Sperre durch Entfernen der Sperre aufgehoben werden.

Weitere Informationen zu Bash-Sperren finden Sie auf dieser Seite

OrangeTux
quelle
1
Sie möchten auch eine EXIT-Falle, die die Sperre beim Verlassen entfernt. echo "Locking succeeded" >&2; trap 'rm -rf /var/lock/mylock' EXIT
Geirha
1
Im Idealfall möchten Sie die Advisory Flock von einem Prozess aus verwenden, der den gewünschten Befehl als Unteraufgabe ausführt. Auf diese Weise wird die Herde automatisch freigegeben, wenn alle sterben, was bei Vorhandensein einer Sperrdatei nicht der Fall ist. Die Verwendung eines Netzwerkanschlusses würde auf ähnliche Weise funktionieren - obwohl dies ein viel kleinerer Namespace ist, was ein Problem darstellt.
Alex North-Keys
3

Keine Notwendigkeit, ein ausgefallenes Paket zu installieren:

#!/bin/bash
pgrep -xf "$*" > /dev/null || "$@"

Es ist schneller, dieses Skript selbst zu schreiben, als "apt-get install" auszuführen, nicht wahr? Möglicherweise möchten Sie dem pgrep "-u $ (id -u)" hinzufügen, um nach Instanzen zu suchen, die nur vom aktuellen Benutzer ausgeführt werden.

Michael Kowhan
quelle
2
Dies garantiert keine einzelne Instanz. Zwei Skripte können gleichzeitig an die andere Seite des ||Operators übergeben werden, bevor einer die Möglichkeit hat, das Skript noch zu starten.
Sedat Kapanoglu
@SedatKapanoglu Zugegeben, dieses Skript ist nicht rennenzustandsfest, aber die ursprüngliche Frage betraf lang laufende Cron-Jobs (die höchstens einmal pro Minute ausgeführt werden). Wenn Ihr System mehr als eine Minute für die Erstellung des Prozesses benötigt, treten einige andere Probleme auf. Falls dies jedoch aus anderen Gründen erforderlich sein sollte, können Sie flock (1) verwenden, um das obige Skript vor Wettkampfbedingungen zu schützen.
Michael Kowhan
Ich habe dies aber für ein Bash-Skript verwendet, das sich selbst überprüfen sollte. Der Code lautet: v = $ (pgrep -xf "/ bin / bash $ 0 $ @") ["$ {v / $ BASHPID /}"! = ""] && Ausfahrt 2
ahofmann
3

Siehe auch Tim Kays solo, der das Sperren durch Binden eines Ports an eine für den Benutzer eindeutige Loopback-Adresse durchführt:

http://timkay.com/solo/

Falls seine Seite ausfällt:

Verwendung:

solo -port=PORT COMMAND

where
    PORT        some arbitrary port number to be used for locking
    COMMAND     shell command to run

options
    -verbose    be verbose
    -silent     be silent

Benutze es so:

* * * * * solo -port=3801 ./job.pl blah blah

Skript:

#!/usr/bin/perl -s
#
# solo v1.7
# Prevents multiple cron instances from running simultaneously.
#
# Copyright 2007-2016 Timothy Kay
# http://timkay.com/solo/
#
# It is free software; you can redistribute it and/or modify it under the terms of either:
#
# a) the GNU General Public License as published by the Free Software Foundation;
#    either version 1 (http://dev.perl.org/licenses/gpl1.html), or (at your option)
#    any later version (http://www.fsf.org/licenses/licenses.html#GNUGPL), or
#
# b) the "Artistic License" (http://dev.perl.org/licenses/artistic.html), or
#
# c) the MIT License (http://opensource.org/licenses/MIT)
#

use Socket;

alarm $timeout                              if $timeout;

$port =~ /^\d+$/ or $noport                     or die "Usage: $0 -port=PORT COMMAND\n";

if ($port)
{
    # To work with OpenBSD: change to
    # $addr = pack(CnC, 127, 0, 1);
    # but make sure to use different ports across different users.
    # (Thanks to  www.gotati.com .)
    $addr = pack(CnC, 127, $<, 1);
    print "solo: bind ", join(".", unpack(C4, $addr)), ":$port\n"   if $verbose;

    $^F = 10;           # unset close-on-exec

    socket(SOLO, PF_INET, SOCK_STREAM, getprotobyname('tcp'))       or die "socket: $!";
    bind(SOLO, sockaddr_in($port, $addr))               or $silent? exit: die "solo($port): $!\n";
}

sleep $sleep if $sleep;

exec @ARGV;
DNA
quelle
Unter Mac OS X schlägt dies mit Fehler fehl, es solo(3801): Can't assign requested addresssei denn, Sie erzwingen einen Fehler 0für den 3. Parameter der Methode pack. Was für das BSD gut ist, ist auch für den Mac gut.
Eric Leschinski
1

Du brauchst ein Schloss. run-oneerledigt den Job, aber Sie möchten vielleicht auch flockaus dem util-linuxPaket schauen .

Es ist ein Standardpaket, das von den Kernel-Entwicklern bereitgestellt wird, mehr Anpassungsmöglichkeiten run-onebietet als und immer noch sehr einfach ist.

Styroporfliege
quelle
0

Eine einfache Lösung von bash-hackers.org , die für mich funktionierte, war mkdir . Auf diese Weise können Sie auf einfache Weise sicherstellen, dass nur eine Instanz Ihres Programms ausgeführt wird. Erstellen Sie mit mkdir .lock ein Verzeichnis, das zurückgegeben wird

  • wahr, wenn die Erstellung erfolgreich war und
  • false, wenn die Sperrdatei vorhanden ist, was darauf hinweist, dass derzeit eine Instanz ausgeführt wird.

Diese einfache Funktion hat also die gesamte Dateisperrlogik ausgeführt:

if mkdir .lock; then
    echo "Locking succeeded"
    eval startYourProgram.sh ;
else
    echo "Lock file exists. Program already running? Exit. "
    exit 1
fi


echo "Program finished, Removing lock."
rm -r .lock
domih
quelle
0

Diese Lösung ist für ein Bash-Skript gedacht, das sich selbst überprüfen muss

v=$(pgrep -xf "/bin/bash $0 $@")
[ "${v/$BASHPID/}" != "" ] && exit 0
ahofmann
quelle