it-swarm-eu.dev

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

J'ai planifié une tâche cron pour qu'elle s'exécute toutes les minutes, mais parfois le script prend plus d'une minute pour se terminer et je ne veux pas que les tâches commencent à "s'empiler" les unes sur les autres. Je suppose 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 fait rechercher au script l'existence d'un fichier particulier ("lockfile.txt") et quitter s'il existe ou touch s'il ne l'est pas. Mais c'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?

97
Tom

Il existe quelques programmes qui automatisent cette fonctionnalité, éliminent la gêne et les bugs potentiels de le faire vous-même et évitent le problème de verrouillage périmé en utilisant flock en arrière-plan également (ce qui est un risque si vous utilisez simplement le toucher) . J'ai utilisé lockrun et lckdo dans 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
124
womble

La meilleure façon dans Shell est d'utiliser flock (1)

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

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

En s'appuyant sur exemple de womble , vous écririez quelque chose comme:

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

BTW, en regardant le code, tous les flock, lockrun et lckdo font exactement la même chose, donc c'est juste une question dont vous avez le plus facilement accès.

23
Amir

Vous n'avez pas spécifié si vous souhaitez que le script attende la fin de l'exécution précédente ou non. Par "Je ne veux pas que les jobs commencent" à s’empiler "les uns sur les autres", je suppose que vous indiquez que vous voulez que le script se termine 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

3

Vous pouvez utiliser un fichier de verrouillage. Créez ce fichier au démarrage du script et supprimez-le à la fin. Le script, avant d'exécuter sa routine principale, 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 de démarrage et par de nombreuses autres applications et utilitaires dans les systèmes Unix.

2
Born To Ride

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.service ou ~/.config/systemd/user/myjob.service:

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

Dans /etc/systemd/system/myjob.timer ou ~/.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 --- pas sera lancé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
1
Amir

Cela pourrait également être un signe que vous faites la mauvaise chose. Si vos travaux sont exécutés de si près et si fréquemment, vous devriez peut-être envisager de le déconnecter et d'en faire un programme de style démon.

Votre démon cron ne devrait pas invoquer de 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
dubiousjim

Je recommanderais d'utiliser la commande run-one - beaucoup plus simple que de gérer les verrous. De la documentation:

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. Cela est souvent utile avec les tâches cron, lorsque vous ne souhaitez 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 appartenant à l'utilisateur et correspondant aux commandes et arguments cibles. Notez que run-this-one se bloquera tout en essayant de tuer les processus correspondants, jusqu'à ce que tous les processus correspondants soient morts.

run-one-constant fonctionne exactement comme run-one sauf qu'il réapparaît "COMMAND [ARGS]" à chaque fois que COMMAND se termine (zéro ou non nul) .

keep-one-running est un alias pour run-one-constant.

run-one-until-success fonctionne exactement comme run-one-constant, sauf qu'il réapparaît "COMMAND [ARGS]" jusqu'à ce que COMMAND se termine avec succès (c'est-à-dire, quitte zéro).

run-one-until-failure fonctionne exactement comme run-one-constant sauf qu'il réapparaît "COMMAND [ARGS]" jusqu'à ce que COMMAND se termine avec échec (ie , sort non nul).

1
Yurik

J'ai créé un bocal pour résoudre un tel problème, comme des crons en double en cours d'exécution pourraient être Java ou Shell cron. pid existant pour ce cron sauf le courant. J'ai implémenté une méthode pour faire ce truc. String proname = 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
Sachin Patil

La réponse @Philip Reynolds commencera de toute façon à exécuter le code après le temps d'attente de 5 s sans obtenir le verrou. Suivant 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 les 5 secondes d'attente, le processus se terminera avec 1 s'il n'a pas obtenu le verrou d'ici là.

0
user__42