Question Empêcher l'exécution de tâches cron en double


J'ai planifié l'exécution d'un travail cron toutes les minutes, mais parfois, le script prend plus d'une minute et je ne veux pas que les travaux commencent à se superposer les uns aux autres. J’imagine que c’est un problème de concurrence, c’est-à-dire que l’exécution du script doit s’exclure mutuellement.

Pour résoudre le problème, j’ai demandé au script de rechercher l’existence d’un fichier particulier ("lockfile.txt") et quitter s'il existe ou touch ça si ce n'est pas le cas. Mais ceci est un sémaphore assez moche! Existe-t-il une meilleure pratique que je devrais connaître? Aurais-je dû écrire un démon à la place?


81
2017-11-09 11:32


origine




Réponses:


Plusieurs programmes automatisent cette fonctionnalité, éliminent la gêne et les bugs potentiels, et évitent le problème de verrouillage obsolète en utilisant également flock dans les coulisses (ce qui constitue un risque si vous utilisez uniquement le toucher). . J'ai utilisé lockrun et lckdo dans le passé, mais maintenant il y a flock(1) (dans les nouvelles versions de util-linux), ce qui est excellent. C'est vraiment facile à utiliser:

* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job

109
2017-11-09 11:57



lckdo va être retiré de moreutils, maintenant que flock (1) est en util-linux. Et ce paquet est fondamentalement obligatoire dans les systèmes Linux, vous devriez donc pouvoir compter sur sa présence. Pour une utilisation, regardez ci-dessous. - jldugger
Oui, le troupeau est maintenant mon option préférée. Je vais même mettre à jour ma réponse en fonction. - womble♦
Est-ce que quelqu'un sait la différence entre flock -n file command et flock -n file -c command ? - Nanne
@ Nanne, je devrais vérifier le code pour en être sûr, mais je suppose que c'est -c exécute la commande spécifiée via un shell (comme indiqué dans la page de manuel), tandis que le paramètre "bare" (non-c) forme juste execs la commande donnée. Passer quelque chose à travers le shell vous permet de faire des choses semblables à celles du shell (comme exécuter plusieurs commandes séparées avec ; ou &&), mais vous ouvre également la porte aux attaques d’extension du shell si vous utilisez des entrées non fiables. - womble♦
C'était un argument à la (hypothétique) frequent_cron_job commande qui a essayé de montrer qu'il était exécuté chaque minute. Je l'ai enlevé car il n'a ajouté rien d'utile, et a causé de la confusion (le vôtre, si personne d'autre ne l'a été au fil des ans). - womble♦


Le meilleur moyen en shell est d'utiliser troupeau (1)

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

27
2017-11-09 11:45



Je ne peux pas éviter une utilisation délicate de la redirection fd. C'est trop arcanement génial. - womble♦
Ne pas analyser pour moi dans Bash ou ZSH, besoin d'éliminer l'espace entre 99 et > donc c'est 99> /... - Kyle Brandt♦
@ Javier: Cela ne veut pas dire que ce n'est pas compliqué et obscur, c'est juste que c'est documenté, délicat, et mystérieux. - womble♦
que se passerait-il si vous redémarrez en cours d'exécution ou si le processus est tué d'une manière ou d'une autre? Serait-il verrouillé pour toujours alors? - Alex R
Je comprends que cette structure crée un verrou exclusif, mais je ne comprends pas la mécanique de la façon dont cela est accompli. Quelle est la fonction du '99' dans cette réponse? Quelqu'un veut expliquer cela s'il vous plaît? Merci! - Asciiom


Réellement, flock -n peut être utilisé à la place de lckdo*, vous utiliserez donc le code des développeurs du noyau.

Bâtir sur l'exemple de womble, vous écririez quelque chose comme:

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

BTW, en regardant le code, tous flock, lockrun, et lckdo faites exactement la même chose, c'est donc une question qui vous est le plus facilement accessible.

* Étant donné ma réputation au moment de la rédaction, je ne peux ni éditer, ni commenter les réponses précédentes, je dois écrire ceci comme réponse séparée.


21
2017-11-19 22:43





Vous pouvez utiliser un fichier de verrouillage. Créez ce fichier au démarrage du script et supprimez-le à la fin. Avant d'exécuter sa routine principale, le script doit vérifier si le fichier de verrouillage existe et procéder en conséquence.

Les fichiers de verrouillage sont utilisés par les scripts d'initialisation et par de nombreuses autres applications et utilitaires dans les systèmes Unix.


2
2017-11-09 11:36



c'est le seulement comme je l’ai jamais vue implémentée personnellement. J'utilise sur la suggestion du responsable comme miroir pour un projet de logiciel libre - warren


Cela pourrait également être un signe que vous faites la mauvaise chose. Si vos tâches sont exécutées aussi étroitement et aussi fréquemment, vous devriez peut-être envisager de la décoder et d'en faire un programme de type démon.


1
2017-11-09 11:45



Je ne suis pas du tout d'accord avec cela. Si vous avez quelque chose à exécuter périodiquement, en faire un démon est une solution de type "sledgehammer for a nut". Utiliser un fichier de verrouillage pour prévenir les accidents est une solution parfaitement raisonnable que je n’ai jamais eu de mal à utiliser. - womble♦
@womble je suis d'accord; mais j'aime casser des noix avec des sledgehammers! :-) - wzzrd


Vous n'avez pas spécifié si vous voulez que le script attende que l'exécution précédente soit terminée ou non. Par "Je ne veux pas que les travaux commencent à" s'empiler "les uns sur les autres", je suppose que vous sous-entendez que vous voulez que le script se ferme s'il est déjà en cours d'exécution,

Donc, si vous ne voulez pas dépendre de lckdo ou similaire, vous pouvez faire ceci:


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


1
2017-11-09 14:33



Merci, votre exemple est utile - je souhaite que le script se ferme s'il est déjà en cours d'exécution. Merci d'avoir mentionné ickdo - il semble faire l'affaire. - Tom


Votre démon cron ne devrait pas appeler des travaux si leurs instances précédentes sont toujours en cours d'exécution. Je suis le développeur d'un démon cron dcron, et nous essayons spécifiquement d’empêcher cela. Je ne sais pas comment Vixie Cron ou d'autres démons gèrent cela.


1
2018-02-17 15:59





Je recommanderais d'utiliser run-one commande - beaucoup plus simple que de traiter avec les serrures. De la docs:

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 voulez pas exécuter plus d’une copie à la fois.

courir-celui-ci est exactement comme run-one, sauf qu'il utilisera pgrep et kill pour rechercher et tuer tous les processus en cours appartenant à l'utilisateur et faire correspondre les commandes et arguments cibles. Notez que run-this-one bloquera tout en essayant de tuer les processus correspondants, jusqu'à ce que tous les processus sont morts.

courir-un-constamment fonctionne exactement comme run-one sauf que respawns "COMMAND [ARGS]" à tout moment, sortie de COMMAND (zéro ou non-zéro).

Keep-one-running est un alias pour run-one-constamment.

courir-un-jusqu'à-réussir fonctionne exactement comme run-one-constamment, sauf qu'il réapparaît "COMMAND [ARGS]" jusqu'à la fin de COMMAND (c'est-à-dire, quitte zéro).

run-one-jusqu'à-échec fonctionne exactement comme run-one-constamment, sauf qu'il réapparaît "COMMAND [ARGS]" jusqu'à ce que COMMAND se termine avec un échec (c'est-à-dire, sorties non nulles).


1
2017-10-02 21:53





J'ai créé un bocal pour résoudre un problème tel que les doublons de crons exécutés peuvent être java ou cron shell. Il suffit de passer le nom du cron dans Duplicates.CloseSessions ("Demo.jar") pour rechercher et tuer le pid existant pour ce cron, sauf current. J'ai implémenté une méthode pour faire ce genre de choses. Proname de chaîne = ManagementFactory.getRuntimeMXBean (). GetName ();                 String pid = proname.split ("@") [0];                 System.out.println ("PID actuel:" + 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+" ";    
                }
            }

Et puis tuez la chaîne killid avec à nouveau la commande shell


0
2017-12-28 08:36



Je ne pense pas que cela répond vraiment à la question. - kasperd