Execute o trabalho cron apenas se ele ainda não estiver em execução

136

Então, estou tentando configurar um trabalho cron como uma espécie de watchdog para um daemon que eu criei. Se o daemon errar e falhar, quero que a tarefa cron o reinicie periodicamente ... Não tenho certeza de como isso é possível, mas li alguns tutoriais do cron e não consegui encontrar nada que fizesse o que eu fazia. estou procurando ...

Meu daemon é iniciado a partir de um script de shell, então estou apenas procurando uma maneira de executar um trabalho cron SOMENTE se a execução anterior desse trabalho ainda não estiver em execução.

Encontrei este post , que forneceu uma solução para o que estou tentando fazer usando arquivos de bloqueio, não sei se existe uma maneira melhor de fazê-lo ...

Obrigado pela ajuda.

LorenVS
fonte

Respostas:

120

Eu faço isso para um programa de spooler de impressão que escrevi, é apenas um script de 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

É executado a cada dois minutos e é bastante eficaz. Eu tenho um e-mail com informações especiais se, por algum motivo, o processo não estiver em execução.

jjclarkson
fonte
4
não é uma solução muito segura, e se houver outro processo que corresponda à pesquisa que você fez no grep? A resposta de rsanden evita esse tipo de problema usando um pidfile.
Elias Dorneles
12
Esta roda já foi inventada em outro lugar :) Por exemplo, serverfault.com/a/82863/108394
Filipe Correia
5
Em vez de grep -v grep | grep doctype.phpvocê pode fazer grep [d]octype.php.
AlexT
Observe que não há necessidade de &se o cron estiver executando o script.
Lainatnavi 28/11/19
124

Usar flock . É novo. É melhor.

Agora você não precisa escrever o código sozinho. Confira mais motivos aqui: https://serverfault.com/a/82863

/usr/bin/flock -n /tmp/my.lockfile /usr/local/bin/my_script
Jess
fonte
1
Uma solução muito fácil
MFB
4
Melhor solução, estou usando isso há muito tempo.
søger
1
Eu também criou uma essência agradável cron modelo aqui: gist.github.com/jesslilly/315132a59f749c11b7c6
Jess
3
setlock, s6-setlock, chpst, E runlocknos seus modos sem bloqueio são alternativas que estão disponíveis em mais do que apenas Linux. unix.stackexchange.com/a/475580/5132
JdeBP 30/10
3
Eu sinto que esta deve ser a resposta aceita. Tão simples!
Codemonkey 15/05/19
62

Como outros já declararam, escrever e verificar um arquivo PID é uma boa solução. Aqui está minha implementação do 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
fonte
3
+1 Usando um arquivo pid é provavelmente muito mais seguro do que o grepping para um programa em execução com o mesmo nome.
Elias Dorneles
/ path / to / myprogram &> $ HOME / tmp / myprogram.log & ?????? fez você talvez média / path / to / meuprograma >> $ HOME / tmp / myprogram.log &
Matteo
1
O arquivo não deve ser removido quando o script terminar? Ou estou perdendo algo muito óbvio?
21714 Hamzahfrq
1
@matteo: Sim, você está certo. Eu havia corrigido isso nas minhas anotações anos atrás, mas esqueci de atualizá-la aqui. Pior, eu também perdi no seu comentário, percebendo apenas " >" versus " >>". Me desculpe por isso.
rsanden
5
@Hamzahfrq: Veja como funciona: O script primeiro verifica se o arquivo PID existe (" [ -e "${PIDFILE}" ]". Se não existir , iniciará o programa em segundo plano, gravará seu PID em um arquivo (" echo $! > "${PIDFILE}"") e Se o arquivo PID existir, o script verificará seus próprios processos (" ps -u $(whoami) -opid=") e verificará se você está executando um com o mesmo PID (" grep -P "^\s*$(cat ${PIDFILE})$""). Caso contrário, ele iniciará o programa como antes, substituir o arquivo PID com o novo PID, e sair Eu não vejo nenhuma razão para modificar o script;.? você
rsanden
35

É surpreendente que ninguém tenha mencionado corrida . Eu resolvi meu problema com isso.

 apt-get install run-one

então adicione run-one antes do seu script crontab

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

Confira esta resposta do askubuntu SE. Você também pode encontrar um link para informações detalhadas.

Bedi Egilmez
fonte
22

Não tente fazer isso via cron. Faça com que o cron execute um script, não importa o quê, e faça com que o script decida se o programa está sendo executado e inicie-o, se necessário (observe que você pode usar Ruby ou Python ou sua linguagem de script favorita para fazer isso)

Earlz
fonte
5
A maneira clássica é ler um arquivo PID que o serviço cria quando é iniciado, verificar se o processo com esse PID ainda está em execução e reiniciar se não estiver.
precisa saber é o seguinte
9

Você também pode fazer isso como uma linha diretamente no seu crontab:

* * * * * [ `ps -ef|grep -v grep|grep <command>` -eq 0 ] && <command>
quezacoatl
fonte
5
não muito seguro, e se houver outros comandos que correspondam à pesquisa por grep?
Elias Dorneles
1
Isso também pode ser escrito como * * * * * [ ps -ef|grep [c]ommand-eq 0] && <comando>, em que a quebra da primeira letra do seu comando entre colchetes a exclui dos resultados do grep.
Jim Clouse
Eu tive que usar a seguinte sintaxe:[ "$(ps -ef|grep [c]ommand|wc -l)" -eq 0 ] && <command>
thameera
1
Isso é horrível. [ $(grep something | wc -l) -eq 0 ]é uma maneira realmente indireta de escrever ! grep -q something. Então você quer simplesmenteps -ef | grep '[c]ommand' || command
tripleee
(Além disso, como um aparte, se você realmente queria para realmente contar o número de linhas correspondentes, que de grep -c.)
tripleee
7

A maneira como faço quando estou executando scripts php é:

O crontab:

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

O código php:

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

Este comando está pesquisando na lista de processos do sistema o nome do arquivo php atual, se existir, o contador de linha (wc -l) será maior que um, porque o próprio comando de pesquisa que contém o nome do arquivo

portanto, se você estiver executando o php crons, adicione o código acima ao início do seu código php e ele será executado apenas uma vez.

talsibony
fonte
Era disso que eu precisava, pois todas as outras soluções exigiam a instalação de algo no servidor do cliente, ao qual não tenho acesso.
perfil completo de Jeff Davis
5

Como resposta à resposta do Earlz, você precisa de um script de wrapper que crie um arquivo $ PID.running quando for iniciado e exclua quando terminar. O script do wrapper chama o script que você deseja executar. O wrapper é necessário no caso de o script de destino falhar ou ocorrer erros, o arquivo pid é excluído.

Byron Whitlock
fonte
Ah, legal ... Eu nunca pensei em usar um invólucro ... Não consegui descobrir uma maneira de fazê-lo usando arquivos de bloqueio, porque não podia garantir que o arquivo fosse excluído se o daemon falhasse ... A envoltório iria funcionar perfeitamente, eu vou dar a solução da jjclarkson um tiro, mas eu vou fazer isso se isso não funcionar ...
LorenVS
3

Eu recomendaria usar uma ferramenta existente, como o monit , ele monitorará e reiniciará automaticamente os processos. Há mais informações disponíveis aqui . Deve estar facilmente disponível na maioria das distribuições.

Zitrax
fonte
Todas as respostas, exceto esta, respondem à pergunta superficial "Como meu trabalho cron pode garantir que apenas execute uma instância?" quando a pergunta real é "Como posso manter meu processo em execução diante das reinicializações?", e a resposta correta é de fato não usar cron, mas um supervisor de processo como monit. Outras opções incluem runit , s6 ou, se sua distribuição já usa systemd, apenas criando um serviço systemd para o processo que precisa ser mantido ativo.
Clacke
3

Este nunca me falhou:

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}

trabalho cron :

* * * * * /path/to/one.sh <command>
DJV
fonte
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
}

ATUALIZAÇÃO .. melhor maneira de usar rebanho:

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

Sugiro o seguinte como uma melhoria na resposta de rsanden (eu postaria como um comentário, mas não tenho reputação suficiente ...):

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

Isso evita possíveis correspondências falsas (e a sobrecarga do grepping) e suprime a saída e depende apenas do status de saída do ps.

dbenton
fonte
1
Seu pscomando corresponderia aos PIDs de outros usuários do sistema, não apenas dos seus. Adicionar um " -u" ao pscomando altera a maneira como o status de saída funciona.
rsanden
1

PHP personalizado simples é suficiente para alcançar. Não há necessidade de confundir com o shell script.

vamos assumir que você deseja executar o php /home/mypath/example.php se não estiver executando

Em seguida, use o seguinte script php personalizado para fazer o mesmo trabalho.

crie o seguinte /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);
    }
?>

Em seguida, no seu cron, adicione o seguinte

* * * * * php /home/mypath/forever.php 'php /home/mypath/example.php'
lingeshram
fonte
0

Considere usar o pgrep (se disponível) em vez do ps canalizado através do grep se você for seguir esse caminho. Embora, pessoalmente, eu tenha muita quilometragem dos scripts do formulário

while(1){
  call script_that_must_run
  sleep 5
}

Embora isso possa falhar e os trabalhos cron geralmente são a melhor maneira de coisas essenciais. Apenas outra alternativa.

Richard Thomas
fonte
2
Isso apenas iniciaria o daemon repetidamente e não resolve o problema mencionado acima.
Cwoebker 5/05
0

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

solo é um script muito simples (10 linhas) que impede que um programa execute mais de uma cópia por vez. É útil com o cron garantir que um trabalho não seja executado antes que um anterior seja concluído.

Exemplo

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