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

92

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 qu'il s'agit d'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 de le quitter s’il existait ou touchnon. 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?

À M
la source

Réponses:

118

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 présente un risque si vous utilisez uniquement le toucher). . J'ai déjà utilisé lockrunet lckdodans le passé, mais maintenant il y a flock(1) (dans les nouvelles versions d'util-linux), ce qui est génial. C'est vraiment facile à utiliser:

* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job
femme
la source
2
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 commandet flock -n file -c command?
Nanne
2
@Nanne, il faudrait que je vérifie le code pour en être sûr, mais mon hypothèse est que -cla commande spécifiée est exécutée via un shell (comme indiqué dans la page de manuel), tandis que la forme "nue" (non -c) est simplement execla commande donnée . Insérer quelque chose dans le shell vous permet d'effectuer des opérations similaires à celles du shell (telles que l'exécution de plusieurs commandes séparées avec ;ou &&), mais vous ouvre également la porte aux attaques de développement du shell si vous utilisez une entrée non fiable.
femme
1
C'était un argument pour la frequent_cron_jobcommande (hypothétique) qui essayait de montrer qu'elle s'exécutait toutes les minutes. 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).
femme
28

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

(
  flock -x -w 5 99
  ## Do your stuff here
) 99>/path/to/my.lock
Philip Reynolds
la source
1
Je ne peux pas éviter une utilisation délicate de la redirection fd. C'est trop arcanement génial.
womble
1
Ne pas analyser pour moi dans Bash ou ZSH, besoin d'éliminer l'espace entre 99et >c'est ainsi99> /...
Kyle Brandt
2
@ Javier: Cela ne veut pas dire que ce n'est pas compliqué et mystérieux, mais que c'est documenté , compliqué et obscur.
womble
1
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
5
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
22

En fait, flock -npeut être utilisé à la place de lckdo*, vous utiliserez donc le code des développeurs du noyau.

En vous basant sur l'exemple de womble , vous écririez quelque chose comme:

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

BTW, regardant le code, tous flock, lockrunet lckdofaire exactement la même chose, il est donc juste une question de ce qui est plus facilement disponible pour vous.

Amir
la source
2

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.

Né pour rouler
la source
1
c'est la seule façon que j'ai jamais vue la mettre en œuvre, personnellement. Je l'utilise conformément à la suggestion du responsable comme miroir pour un projet OSS
warren
2

Vous n'avez pas spécifié si vous voulez que le script attende ou non l'exécution précédente. 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 le faire:


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
la source
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 - cela semble faire l'affaire.
Tom
FWIW: J'aime cette solution car elle peut être incluse dans un script. Le verrouillage fonctionne donc quelle que soit la manière dont le script est appelé.
David G
1

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.


la source
3
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
1

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 précisément d'empêcher cela. Je ne sais pas comment Vixie Cron ou d'autres démons gèrent cela.

douteux
la source
1

Je recommanderais d'utiliser la commande run-one - beaucoup plus simple que de traiter avec les verrous. 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.

run-this-one est exactement comme run-one, sauf qu'il utilisera pgrep et kill pour rechercher et tuer tous les processus en cours d'exécution détenus par l'utilisateur et correspondant aux commandes et arguments cibles. Notez que run-this-one bloquera tout en essayant de tuer les processus correspondants jusqu'à ce que tous les processus correspondants soient morts.

run-one- fonctionne exactement de la même manière que run-one, sauf qu'il rappelle "COMMAND [ARGS]" à chaque sortie de COMMAND (zéro ou différent de zéro).

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

run-one-Until-Success fonctionne exactement comme run-one-constant sauf qu'il rappelle "COMMAND [ARGS]" jusqu'à ce que COMMAND se ferme correctement (c.-à-d. qu'il quitte zéro).

run-one-jusqu'à-échec fonctionne exactement comme run-one-constamment, sauf qu'il rappelle "COMMAND [ARGS]" jusqu'à ce que COMMAND se termine avec un échec (c'est-à-dire des sorties différentes de zéro).

Yurik
la source
1

Maintenant que systemd est sorti, il existe un autre mécanisme de planification sur les systèmes Linux:

UNE systemd.timer

Dans /etc/systemd/system/myjob.serviceou ~/.config/systemd/user/myjob.service:

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

Dans /etc/systemd/system/myjob.timerou ~/.config/systemd/user/myjob.timer:

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target

Si l'unité de service est déjà activée lors de l'activation suivante du minuteur, une autre instance du service ne sera pas démarrée.

Une alternative, qui démarre le travail une fois au démarrage et une minute après la fin de chaque exécution:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target
Amir
la source
0

J'ai créé un bocal pour résoudre un problème tel que les doublons de crons exécutés pourraient ê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 mis en place 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

Sachin Patil
la source
Je ne pense pas que cela répond vraiment à la question.
Kasperd
0

@Philip Reynolds answer commencera à exécuter le code après le délai d’attente de 5 secondes sans obtenir le verrou. Après Flock ne semble pas fonctionner, j'ai modifié la réponse de @Philip Reynolds à

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

afin que le code ne soit jamais exécuté simultanément. Au lieu de cela, après 5 secondes d’attente, le processus se termine avec 1 s’il n’a pas encore obtenu le verrou.

utilisateur__42
la source