Solution générique pour empêcher un long travail cron de s'exécuter en parallèle?

27

Je recherche une solution simple et générique qui vous permettrait d'exécuter n'importe quel script ou application dans crontab et de l'empêcher de s'exécuter deux fois.

La solution doit être indépendante de la commande exécutée.

Je suppose que cela devrait ressembler à l' lock && (command ; unlock)endroit où le verrou retournera faux s'il y avait un autre verrou.

La deuxième partie serait comme si elle avait acquis le verrou, exécuté la commande et déverrouillé après l'exécution de la commande, même si elle renvoie une erreur.

Sorin
la source

Réponses:

33

Jetez un œil au package run-oneInstaller run-one . Depuis la page de manuel de la run-onecommandeIcône de page de manuel :

run-one est un script wrapper qui n'exécute pas plus d'une instance unique d'une commande avec un ensemble unique d'arguments.

Ceci est souvent utile avec les tâches cron, lorsque vous ne souhaitez pas exécuter plus d'une copie à la fois.

Comme timeou sudo, vous l'ajoutez simplement à la commande. Ainsi, un cronjob pourrait ressembler à:

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

Pour plus d'informations et d'informations, consultez le billet de blog le présentant par Dustin Kirkland.

andrewsomething
la source
5

Un moyen très simple de régler une serrure:

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

Un script qui veut s'exécuter doit créer le verrou. Si le verrou existe, un autre script est occupé, le premier script ne peut donc pas s'exécuter. Si le fichier n'existe pas, aucun script n'a acquis le verrou. Le script actuel acquiert donc le verrou. Une fois le script terminé, le verrou doit être libéré en retirant le verrou.

Pour plus d'informations sur les verrous bash, consultez cette page

OrangeTux
la source
1
Vous aurez également besoin d'un piège EXIT qui supprime le verrou à la sortie. echo "Locking succeeded" >&2; trap 'rm -rf /var/lock/mylock' EXIT
geirha
1
Idéalement, vous souhaitez utiliser le troupeau consultatif à partir d'un processus qui exécute la commande souhaitée en tant que sous-tâche. De cette façon, s'ils meurent tous, le troupeau est libéré automatiquement, ce qui n'est pas le cas en utilisant la présence d'un fichier de verrouillage. L'utilisation d'un port réseau fonctionnerait d'une manière similaire - bien que ce soit un espace de noms beaucoup plus petit, ce qui est un problème.
Alex North-Keys
3

Pas besoin d'installer un paquet fantaisie:

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

Il est plus rapide d'écrire ce script vous-même que d'exécuter "apt-get install", n'est-ce pas? Vous voudrez peut-être ajouter "-u $ (id -u)" au pgrep pour vérifier uniquement les instances exécutées par l'utilisateur actuel.

Michael Kowhan
la source
2
cela ne garantit pas une seule instance. deux scripts peuvent passer de l'autre côté de l' ||opérateur en même temps, avant que l'un ou l'autre n'ait encore la possibilité de démarrer le script.
Sedat Kapanoglu
@SedatKapanoglu Certes, ce script n'est pas à l'épreuve des conditions de course, mais la question d'origine concernait les tâches cron de longue durée (qui sont exécutées au plus une fois par minute). Si votre système a besoin de plus d'une minute pour la création du processus, vous avez d'autres problèmes. Cependant, si nécessaire pour d'autres raisons, vous pouvez utiliser flock (1) pour protéger le script ci-dessus contre les conditions de course.
Michael Kowhan
Je l'ai utilisé mais pour un script bash qui devrait se vérifier. Le code est le suivant: v = $ (pgrep -xf "/ bin / bash $ 0 $ @") ["$ {v / $ BASHPID /}"! = ""] && exit 2
ahofmann
3

Voir aussi Tim Kay's solo, qui effectue le verrouillage en liant un port à une adresse de bouclage unique à l'utilisateur:

http://timkay.com/solo/

Au cas où son site tombe en panne:

Usage:

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

Utilisez-le comme ceci:

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

Scénario:

#!/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;
ADN
la source
Pour Mac OSX, cela échouera avec Erreur, solo(3801): Can't assign requested addresssauf si vous forcez un 0pour le 3e paramètre de méthode pack. Ce qui est bon pour le BSD l'est aussi pour le Mac.
Eric Leschinski
1

Vous avez besoin d'un verrou. run-onefait le travail, mais vous voudrez peut-être également examiner à flockpartir du util-linuxpackage.

Il s'agit d'un package standard fourni par les développeurs du noyau, qui permet plus de personnalisation run-oneet est toujours très simple.

mouche en polystyrène
la source
0

Une solution simple de bash-hackers.org qui fonctionnait pour moi était d'utiliser mkdir . C'est un moyen simple de s'assurer qu'une seule instance de votre programme est en cours d'exécution. Créez un répertoire avec mkdir .lock qui renvoie

  • vrai si la création a réussi et
  • false si le fichier de verrouillage existe, indiquant qu'il existe actuellement une instance en cours d'exécution.

Donc, cette fonction simple a fait toute la logique de verrouillage de fichier:

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
la source
0

Cette solution est pour un script bash qui doit se vérifier

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