Exécuter le travail cron uniquement s'il n'est pas déjà en cours d'exécution

136

J'essaye donc de mettre en place un travail cron comme une sorte de chien de garde pour un démon que j'ai créé. Si le démon échoue et échoue, je veux que le travail cron le redémarre périodiquement ... Je ne sais pas si c'est possible, mais j'ai lu quelques didacticiels cron et je n'ai rien trouvé qui ferait ce que je je cherche ...

Mon démon démarre à partir d'un script shell, donc je recherche simplement un moyen d'exécuter une tâche cron UNIQUEMENT si l'exécution précédente de cette tâche n'est pas encore en cours d'exécution.

J'ai trouvé cet article , qui fournissait une solution à ce que j'essaie de faire en utilisant des fichiers de verrouillage, mais je ne suis pas sûr qu'il existe une meilleure façon de le faire ...

Merci de votre aide.

LorenVS
la source

Réponses:

120

Je fais cela pour un programme de spouleur d'impression que j'ai écrit, c'est juste un script shell:

#!/bin/sh
if ps -ef | grep -v grep | grep doctype.php ; then
        exit 0
else
        /home/user/bin/doctype.php >> /home/user/bin/spooler.log &
        #mailing program
        /home/user/bin/simplemail.php "Print spooler was not running...  Restarted." 
        exit 0
fi

Il fonctionne toutes les deux minutes et est assez efficace. Je l'ai envoyé par e-mail avec des informations spéciales si, pour une raison quelconque, le processus ne fonctionne pas.

jjclarkson
la source
4
pas une solution très sûre, cependant, que faire s'il y a un autre processus qui correspond à la recherche que vous avez faite dans grep? La réponse de rsanden empêche ce genre de problème en utilisant un fichier pid.
Elias Dorneles
12
Cette roue a déjà été inventée ailleurs :) Par exemple, serverfault.com/a/82863/108394
Filipe Correia
5
Au lieu de grep -v grep | grep doctype.phpvous pouvez le faire grep [d]octype.php.
AlexT
Notez qu'il n'y a pas besoin de &si c'est cron qui exécute le script.
lainatnavi
125

Utilisez flock. C'est nouveau. C'est mieux.

Vous n'avez plus besoin d'écrire le code vous-même. Découvrez plus de raisons ici: https://serverfault.com/a/82863

/usr/bin/flock -n /tmp/my.lockfile /usr/local/bin/my_script
Jess
la source
1
Une solution très simple
MFB
4
Meilleure solution, je l'utilise depuis très longtemps.
soger
1
J'ai également créé un joli modèle de cron ici: gist.github.com/jesslilly/315132a59f749c11b7c6
Jess
3
setlock, s6-setlock, chpstEt runlockdans leurs modes non-blocage des alternatives qui sont disponibles sur plus que Linux. unix.stackexchange.com/a/475580/5132
JdeBP
3
Je pense que cela devrait être la réponse acceptée. Si simple!
Codemonkey
62

Comme d'autres l'ont indiqué, écrire et vérifier un fichier PID est une bonne solution. Voici ma mise en œuvre bash:

#!/bin/bash

mkdir -p "$HOME/tmp"
PIDFILE="$HOME/tmp/myprogram.pid"

if [ -e "${PIDFILE}" ] && (ps -u $(whoami) -opid= |
                           grep -P "^\s*$(cat ${PIDFILE})$" &> /dev/null); then
  echo "Already running."
  exit 99
fi

/path/to/myprogram > $HOME/tmp/myprogram.log &

echo $! > "${PIDFILE}"
chmod 644 "${PIDFILE}"
rsanden
la source
3
+1 Utiliser un fichier pid est probablement beaucoup plus sûr que de chercher un programme en cours d'exécution avec le même nom.
Elias Dorneles
/ chemin / vers / monprogramme &> $ HOME / tmp / monprogramme.log & ?????? avez-vous peut-être voulu dire / chemin / vers / monprogramme >> $ HOME / tmp / myprogram.log &
matteo
1
Le fichier ne devrait-il pas être supprimé une fois le script terminé? Ou est-ce que je rate quelque chose de très évident?
Hamzahfrq
1
@matteo: Oui, vous avez raison. J'avais corrigé cela dans mes notes il y a des années, mais j'ai oublié de le mettre à jour ici. Pire encore, je l'ai manqué dans votre commentaire, ne remarquant que " >" versus " >>". Désolé pour ça.
rsanden le
5
@Hamzahfrq: Voici comment cela fonctionne: Le script vérifie d'abord si le fichier PID existe (" [ -e "${PIDFILE}" ]". Si ce n'est pas le cas, il démarrera le programme en arrière-plan, écrira son PID dans un fichier (" echo $! > "${PIDFILE}""), et quitter. Si le fichier PID existe à la place, le script vérifiera vos propres processus (" ps -u $(whoami) -opid=") et verra si vous en exécutez un avec le même PID (" grep -P "^\s*$(cat ${PIDFILE})$""). Si ce n'est pas le cas, il lancera le programme comme avant, écrasez le fichier PID avec le nouveau PID et quittez. Je ne vois aucune raison de modifier le script; n'est-ce pas?
rsanden
35

Il est surprenant que personne n'ait parlé de run-one . J'ai résolu mon problème avec ça.

 apt-get install run-one

puis ajoutez run-oneavant votre script crontab

*/20 * * * * * run-one python /script/to/run/awesome.py

Découvrez cette réponse askubuntu SE. Vous pouvez également y trouver un lien vers des informations détaillées.

Bedi Egilmez
la source
22

N'essayez pas de le faire via cron. Demandez à cron d'exécuter un script quoi qu'il arrive, puis demandez au script de décider si le programme est en cours d'exécution et de le démarrer si nécessaire (notez que vous pouvez utiliser Ruby ou Python ou votre langage de script préféré pour ce faire)

Earlz
la source
5
La méthode classique consiste à lire un fichier PID que le service crée au démarrage, à vérifier si le processus avec ce PID est toujours en cours d'exécution et à redémarrer dans le cas contraire.
tvanfosson
9

Vous pouvez également le faire en one-liner directement dans votre crontab:

* * * * * [ `ps -ef|grep -v grep|grep <command>` -eq 0 ] && <command>
quezacoatl
la source
5
pas très sûr, et s'il y a d'autres commandes qui correspondent à la recherche de grep?
Elias Dorneles
1
Cela pourrait aussi être écrit comme * * * * * [ ps -ef|grep [c]ommand-eq 0] && <command> où le fait de placer la première lettre de votre commande entre crochets l'exclut des résultats de grep.
Jim Clouse
J'ai dû utiliser la syntaxe suivante:[ "$(ps -ef|grep [c]ommand|wc -l)" -eq 0 ] && <command>
thameera
1
C'est hideux. [ $(grep something | wc -l) -eq 0 ]est une manière vraiment détournée d'écrire ! grep -q something. Donc, vous voulez simplementps -ef | grep '[c]ommand' || command
tripleee
(De plus, en passant, si vous vouliez vraiment compter le nombre de lignes correspondantes, c'est grep -c.)
tripleee
7

La façon dont je le fais lorsque j'exécute des scripts php est:

Le crontab:

* * * * * php /path/to/php/script.php &

Le code php:

<?php
if (shell_exec('ps aux | grep ' . __FILE__ . ' | wc  -l') > 1) {
    exit('already running...');
}
// do stuff

Cette commande recherche dans la liste des processus système le nom de fichier php actuel s'il existe, le compteur de ligne (wc -l) sera supérieur à un car la commande de recherche elle-même contenant le nom de fichier

donc si vous exécutez php crons, ajoutez le code ci-dessus au début de votre code php et il ne fonctionnera qu'une seule fois.

talsibony
la source
C'est ce dont j'avais besoin car toutes les autres solutions nécessitaient d'installer quelque chose sur le serveur client, ce que je n'ai pas accès à faire.
Jeff Davis
5

Pour faire suite à la réponse d'Earlz, vous avez besoin d'un script wrapper qui crée un fichier $ PID.running au démarrage et le supprime lorsqu'il se termine. Le script wrapper appelle le script que vous souhaitez exécuter. L'encapsuleur est nécessaire en cas d'échec du script cible ou d'erreur, le fichier pid est supprimé.

Byron Whitlock
la source
Oh cool ... Je n'ai jamais pensé à utiliser un wrapper ... Je ne pouvais pas trouver un moyen de le faire en utilisant des fichiers verrouillés car je ne pouvais pas garantir que le fichier serait supprimé si le démon se trompait ... wrapper fonctionnerait parfaitement, je vais essayer la solution de jjclarkson, mais je le ferai si cela ne fonctionne pas ...
LorenVS
3

Je recommanderais d'utiliser un outil existant tel que monit , il surveillera et redémarrera automatiquement les processus. Il y a plus d'informations disponibles ici . Il devrait être facilement disponible dans la plupart des distributions.

Zitrax
la source
Toutes les réponses sauf celle-ci répondent à la question de surface "Comment mon travail cron peut-il s'assurer qu'il n'exécute qu'une seule instance?" quand la vraie question est "Comment puis-je continuer à faire fonctionner mon processus face aux redémarrages?", et la bonne réponse est en effet de ne pas utiliser cron, mais plutôt un superviseur de processus comme monit. D'autres options incluent runit , s6 ou, si votre distribution utilise déjà systemd, créer simplement un service systemd pour le processus qui doit être maintenu en vie.
clacke
3

Celui-ci ne m'a jamais manqué:

one.sh :

LFILE=/tmp/one-`echo "$@" | md5sum | cut -d\  -f1`.pid
if [ -e ${LFILE} ] && kill -0 `cat ${LFILE}`; then
   exit
fi

trap "rm -f ${LFILE}; exit" INT TERM EXIT
echo $$ > ${LFILE}

$@

rm -f ${LFILE}

travail cron :

* * * * * /path/to/one.sh <command>
DJV
la source
3
# one instance only (works unless your cmd has 'grep' in it)
ALREADY_RUNNING_EXIT_STATUS=0
bn=`basename $0`
proc=`ps -ef | grep -v grep | grep "$bn" | grep -v " $$ "`
[ $? -eq 0 ] && {
    pid=`echo $proc | awk '{print $2}'`
    echo "$bn already running with pid $pid"
    exit $ALREADY_RUNNING_EXIT_STATUS
}

MISE À JOUR .. meilleure façon d'utiliser flock:

/usr/bin/flock -n /tmp/your-app.lock /path/your-app args 
Ekerner
la source
1

Je suggérerais ce qui suit pour améliorer la réponse de rsanden (je posterais comme commentaire, mais je n'ai pas assez de réputation ...):

#!/usr/bin/env bash

PIDFILE="$HOME/tmp/myprogram.pid"

if [ -e "${PIDFILE}" ] && (ps -p $(cat ${PIDFILE}) > /dev/null); then
  echo "Already running."
  exit 99
fi

/path/to/myprogram

Cela évite d'éventuelles fausses correspondances (et la surcharge de grepping), et supprime la sortie et ne repose que sur l'état de sortie de ps.

Dbenton
la source
1
Votre pscommande correspondrait aux PID des autres utilisateurs du système, pas seulement aux vôtres. L'ajout d'un " -u" à la pscommande modifie le fonctionnement de l'état de sortie.
rsanden le
1

Un simple php personnalisé suffit à atteindre. Pas besoin de confondre avec le script shell.

Supposons que vous vouliez exécuter php /home/mypath/example.php s'il n'est pas en cours d'exécution

Ensuite, utilisez le script PHP personnalisé suivant pour faire le même travail.

créer suivant /home/mypath/forever.php

<?php
    $cmd = $argv[1];
    $grep = "ps -ef | grep '".$cmd."'";
    exec($grep,$out);
    if(count($out)<5){
        $cmd .= ' > /dev/null 2>/dev/null &';
        exec($cmd,$out);
        print_r($out);
    }
?>

Puis dans votre cron, ajoutez ce qui suit

* * * * * php /home/mypath/forever.php 'php /home/mypath/example.php'
lingeshram
la source
0

Pensez à utiliser pgrep (si disponible) plutôt que ps acheminé via grep si vous allez suivre cette route. Bien que, personnellement, j'ai beaucoup de kilomètres à parcourir avec les scripts du formulaire

while(1){
  call script_that_must_run
  sleep 5
}

Bien que cela puisse échouer et que les tâches cron soient souvent le meilleur moyen d'effectuer des tâches essentielles. Juste une autre alternative.

Richard Thomas
la source
2
Cela ne ferait que redémarrer le démon encore et encore et ne résoudrait pas le problème mentionné ci-dessus.
cwoebker
0

Documents: https://www.timkay.com/solo/

solo est un script très simple (10 lignes) qui empêche un programme d'exécuter plus d'une copie à la fois. Il est utile avec cron de s'assurer qu'une tâche ne s'exécute pas avant la fin d'une précédente.

Exemple

* * * * * solo -port=3801 ./job.pl blah blah
Erlang P
la source