Esegui cron job solo se non è già in esecuzione


136

Quindi sto cercando di impostare un lavoro cron come una sorta di cane da guardia per un demone che ho creato. Se il demone si guasta e fallisce, voglio che il processo cron lo riavvii periodicamente ... Non sono sicuro di quanto sia possibile, ma ho letto un paio di tutorial cron e non sono riuscito a trovare nulla che potesse fare quello che sto cercando ...

Il mio demone viene avviato da uno script di shell, quindi sto davvero cercando un modo per eseguire un lavoro cron SOLO se la precedente esecuzione di quel lavoro non è ancora in esecuzione.

Ho trovato questo post , che ha fornito una soluzione per quello che sto cercando di fare usando i file di blocco, non sono sicuro che ci sia un modo migliore per farlo ...

Grazie per l'aiuto.

Risposte:


120

Lo faccio per un programma di spooler di stampa che ho scritto, è solo uno script di 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

Funziona ogni due minuti ed è abbastanza efficace. Ho ricevuto un'email con informazioni speciali se per qualche motivo il processo non è in esecuzione.


4
non è una soluzione molto sicura, e se ci fossero altri processi che corrispondono alla ricerca che hai fatto in grep? La risposta di rsanden evita questo tipo di problema usando un pidfile.
Elias Dorneles,

12
Questa ruota è già stata inventata da qualche altra parte :) Ad esempio, serverfault.com/a/82863/108394
Filipe Correia,

5
Invece di grep -v grep | grep doctype.phpte puoi fare grep [d]octype.php.
AlexT

Si noti che non è necessario &se cron esegue lo script.
lainatnavi,

125

Usa flock. È nuovo. È meglio.

Ora non devi scrivere tu stesso il codice. Scopri altri motivi qui: https://serverfault.com/a/82863

/usr/bin/flock -n /tmp/my.lockfile /usr/local/bin/my_script

1
Una soluzione molto semplice
MFB

4
La soluzione migliore, lo sto usando da molto tempo.
soger,

1
Ho anche creato una bella versione di cron template qui: gist.github.com/jesslilly/315132a59f749c11b7c6
Jess

3
setlock, s6-setlock, chpst, E runlocknelle loro modalità non-bloccanti sono alternative che sono disponibili su più di un semplice Linux. unix.stackexchange.com/a/475580/5132
JdeBP

3
Penso che questa dovrebbe essere la risposta accettata. Così semplice!
Codemonkey

62

Come altri hanno già detto, scrivere e controllare un file PID è una buona soluzione. Ecco la mia implementazione 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}"

3
+1 L'uso di un pidfile è probabilmente molto più sicuro del grepping per un programma in esecuzione con lo stesso nome.
Elias Dorneles,

/ path / to / myprogram &> $ HOME / tmp / myprogram.log & ?????? intendevi forse / path / to / myprogram >> $ HOME / tmp / myprogram.log &
matteo

1
Il file non dovrebbe essere rimosso al termine dello script? O mi sto perdendo qualcosa di molto ovvio?
Hamzahfrq,

1
@matteo: Sì, hai ragione. L'avevo risolto nei miei appunti anni fa, ma ho dimenticato di aggiornarlo qui. Peggio ancora, l'ho perso anche nel tuo commento, notando solo " >" contro " >>". Mi dispiace per quello.
sabato

5
@Hamzahfrq: Ecco come funziona: lo script verifica innanzitutto se il file PID esiste (" [ -e "${PIDFILE}" ]". In caso contrario, avvia il programma in background, scrive il suo PID in un file (" echo $! > "${PIDFILE}"") e exit. Se invece esiste il file PID, lo script controllerà i tuoi processi (" ps -u $(whoami) -opid=") e vedrà se ne stai eseguendo uno con lo stesso PID (" grep -P "^\s*$(cat ${PIDFILE})$""). In caso contrario, avvierà il programma come prima, sovrascrivi il file PID con il nuovo PID ed esci. Non vedo alcun motivo per modificare lo script, vero?
randand

35

È sorprendente che nessuno abbia parlato del run-one . Ho risolto il mio problema con questo.

 apt-get install run-one

quindi aggiungi run-oneprima del tuo script crontab

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

Dai un'occhiata a questa domanda di askubuntu SE. Qui puoi trovare anche un link a informazioni dettagliate.


22

Non provare a farlo tramite cron. Chiedi a cron di eseguire uno script in qualsiasi caso, quindi chiedi allo script di decidere se il programma è in esecuzione e di avviarlo se necessario (nota che puoi usare Ruby o Python o il tuo linguaggio di script preferito per farlo)


5
Il modo classico è leggere un file PID che il servizio crea all'avvio, verificare se il processo con quel PID è ancora in esecuzione e riavviare in caso contrario.
tvanfosson,

9

Puoi anche farlo come one-liner direttamente nel tuo crontab:

* * * * * [ `ps -ef|grep -v grep|grep <command>` -eq 0 ] && <command>

5
non molto sicuro, e se ci fossero altri comandi che corrispondono alla ricerca di grep?
Elias Dorneles,

1
Questo potrebbe anche essere scritto come * * * * * [ ps -ef|grep [c]ommand-eq 0] && <comando> dove racchiudere la prima lettera del comando tra parentesi la esclude dai risultati grep.
Jim Clouse,

Ho dovuto usare la sintassi seguente:[ "$(ps -ef|grep [c]ommand|wc -l)" -eq 0 ] && <command>
thameera

1
Questo è orribile. [ $(grep something | wc -l) -eq 0 ]è un modo davvero rotondeggiante di scrivere ! grep -q something. Quindi vuoi semplicementeps -ef | grep '[c]ommand' || command
tripleee

(Inoltre, se vuoi davvero contare il numero di righe corrispondenti, questo è grep -c.)
Tripleee

7

Il modo in cui lo faccio quando eseguo script php è:

Il crontab:

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

Il codice php:

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

Questo comando sta cercando nell'elenco dei processi di sistema il nome file php corrente se esiste il contatore di linea (wc -l) sarà maggiore di uno perché il comando di ricerca stesso contenente il nome file

quindi se esegui php crons aggiungi il codice sopra all'inizio del tuo codice php e verrà eseguito solo una volta.


Questo è ciò di cui avevo bisogno poiché tutte le altre soluzioni richiedevano l'installazione di qualcosa sul server client, cosa che non ho accesso.
Jeff Davis,

5

Come risposta alla risposta di Earlz, è necessario uno script wrapper che crei un file $ PID.running all'avvio e lo elimini al termine. Lo script wrapper chiama lo script che si desidera eseguire. Il wrapper è necessario nel caso in cui lo script target fallisca o si guasti, il file pid viene eliminato.


Oh figo ... Non ho mai pensato di usare un wrapper ... Non riuscivo a capire un modo per farlo usando i file di blocco perché non potevo garantire che il file sarebbe stato cancellato se il demone avesse sbagliato ... A wrapper funzionerebbe perfettamente, ho intenzione di dare un colpo alla soluzione di jjclarkson, ma lo farò se non funziona ...
LorenVS


3

Consiglierei di utilizzare uno strumento esistente come Monit , che monitorerà e riavvierà automaticamente i processi. Ulteriori informazioni sono disponibili qui . Dovrebbe essere facilmente disponibile nella maggior parte delle distribuzioni.


Ogni risposta, tranne questa, risponde alla domanda di superficie "Come può il mio cron job assicurarsi che esegua solo un'istanza?" quando la vera domanda è "Come posso mantenere il mio processo in esecuzione di fronte ai riavvii?", e la risposta corretta è effettivamente non usare cron, ma invece un supervisore di processo come monit. Altre opzioni includono runit , s6 o, se la distribuzione utilizza già systemd, creando semplicemente un servizio systemd per il processo che deve essere mantenuto attivo.
clacke,

3

Questo non mi ha mai deluso:

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}

cron job :

* * * * * /path/to/one.sh <command>

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
}

AGGIORNAMENTO .. ​​modo migliore usando flock:

/usr/bin/flock -n /tmp/your-app.lock /path/your-app args 

1

Suggerirei quanto segue come miglioramento della risposta di rsanden ( pubblicherei come commento, ma non ho abbastanza reputazione ...):

#!/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

Questo evita possibili false corrispondenze (e il sovraccarico del grepping) e sopprime l'output e si basa solo sullo stato di uscita di ps.


1
Il tuo pscomando corrisponderebbe ai PID per gli altri utenti del sistema, non solo per i tuoi. L'aggiunta di un " -u" al pscomando cambia il modo in cui funziona lo stato di uscita.
sabato

1

È sufficiente un semplice PHP personalizzato. Non c'è bisogno di confondere con lo script della shell.

supponiamo che tu voglia eseguire php /home/mypath/example.php se non in esecuzione

Quindi utilizzare il seguente script php personalizzato per fare lo stesso lavoro.

crea il seguente /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);
    }
?>

Quindi nel tuo cron aggiungi seguente

* * * * * php /home/mypath/forever.php 'php /home/mypath/example.php'

0

Prendi in considerazione l'utilizzo di pgrep (se disponibile) anziché ps inoltrato tramite grep se intendi seguire questa strada. Anche se, personalmente, ho un sacco di chilometraggio dagli script del modulo

while(1){
  call script_that_must_run
  sleep 5
}

Anche se questo può fallire e i lavori cron sono spesso il modo migliore per le cose essenziali. Solo un'altra alternativa.


2
Questo farebbe semplicemente ricominciare il demone più e più volte e non risolverà il problema sopra menzionato.
cwoebker,

0

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

solo è uno script molto semplice (10 righe) che impedisce a un programma di eseguire più di una copia alla volta. È utile con cron assicurarsi che un lavoro non venga eseguito prima che uno precedente sia terminato.

Esempio

* * * * * solo -port=3801 ./job.pl blah blah
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.