Usar "while true" para manter um script vivo é uma boa idéia?

19

Estou entrando no unix de outro mundo e queria saber se

while true
do
  /someperlscript.pl
done

O próprio script perl possui internamente um observador de pastas / arquivos que é executado quando os arquivos são alterados no local de destino.

Isso é ( while true) uma boa ideia? Caso contrário, qual é a abordagem robusta preferida?

TIA

Edição: Como isso parece ter gerado um pouco de interesse, aqui está o cenário completo. O próprio script perl observa um diretório usando um observador de arquivos. Ao receber novos arquivos (eles chegam via rsync), ele pega o novo e o processa. Agora, os arquivos recebidos podem estar corrompidos (não pergunte .. provenientes de um raspberry pi) e, às vezes, o processo pode não ser capaz de lidar com isso. Não sei exatamente por que, porque ainda não estamos cientes de todos os cenários.

MAS - se o processo falhar por algum motivo, queremos que ele esteja em funcionamento e lide com o próximo arquivo, porque o próximo arquivo não tem nenhuma relação com o arquivo anterior que pode ter causado o erro.

Normalmente, eu usaria algum tipo de catch all e envolvia todo o código em torno dele para que NUNCA travasse. Mas não tinha certeza do perl.

Pelo que entendi, usar algo como supervisord é uma boa abordagem para isso.

Abhinav Gujjar
fonte
9
É este Linux ou UNIX? Se for Linux, convém verificar a inotifyAPI para evitar um loop ocupado aguardando a alteração dos arquivos no diretório de destino.
roaima
É linux, ubuntu
Abhinav Gujjar
Se você sabe que o arquivo está bom antes de sair do pi, basta executar uma soma de verificação como md5 ou sha1 e enviá-la junto com o arquivo. Em seguida, o receptor saberá se o arquivo está com problemas antes de tentar processá-lo. Se você não souber disso, ainda poderá criar algum tipo de soma de verificação parcial ou em bloco ou algo semelhante em seu arquivo de dados que garanta a integridade dos dados, para que possa verificar as coisas antes de se comprometer com um processo que pode falhar. Uma onça da prevenção ...
Joe

Respostas:

21

Isso depende da rapidez com que o script perl retorna. Se retornar rapidamente, convém inserir uma pequena pausa entre as execuções para evitar a carga da CPU, por exemplo:

while true
do
  /someperlscript.pl
  sleep 1
done

Isso também evitará um problema de CPU se o script não for encontrado ou travar imediatamente.

O loop também pode ser melhor implementado no próprio script perl para evitar esses problemas.

Editar:

Como você escreveu o loop, o único objetivo é reiniciar o script perl, caso ocorra uma falha, uma abordagem melhor seria implementá-lo como um serviço monitorado, mas a maneira precisa de fazer isso depende do sistema operacional. Por exemplo: Solaris smf, Linux systemd ou um cronômetro baseado em cron.

jlliagre
fonte
10
Você poderia fazer while sleep 1; do ...para salvar a chamada verdadeira.
Raphael Ahrens
4
@RaphaelAhrens De fato, embora isso mude um pouco o comportamento inicial. A alternativa until ! sleep 1; do ...; doneainda salvará a chamada interna ao iniciar o script imediatamente.
Jlliagre
4
Concordo totalmente que um loop quente deve ser evitado com uma chamada de suspensão dentro do loop, se você fizer isso. Concorde também que um script orientado a eventos (inotify, et al) seria uma solução melhor. Mas usar um loop while não é intrinsecamente mau, necessariamente, a menos que seja infinito e quente. Eu acho que a questão mais importante provavelmente está lidando com o motivo pelo qual o script perl falha e precisa ser reiniciado.
Craig
2
Melhor: sleep 1& /someperlscript.pl; wait.
user23013
2
@EliahKagan Bem avistado. TBH, nunca uso a untilinstrução, confundi-a com um do/untilloop hipotético que não existe no shell.
Jlliagre
13

As outras respostas, sobre o uso inotify, estão corretas, mas não são uma resposta para esta pergunta.

Um supervisor de processo, como supervisord, upstart, ou runit, é projetado para exatamente o problema de assistir e reiniciar um serviço se ele falhar.

Sua distribuição provavelmente vem com um supervisor de processo incorporado.

Jacob Krall
fonte
11

while trueé bom como uma construção geral de "loop para sempre". Como outras respostas dizem, o corpo do loop não deve estar vazio ou ficar vazio em virtude do comando dentro do loop não funcionar.

Se você estiver usando Linux, convém usar um comando como o inotifywaitque torna o whileloop muito mais simples:

while inotifywait -qqe modify "$DIRECTORY"
do
    process_the_directory "$DIRECTORY"
done

Aqui, o inotifywaitcomando aguardará a ocorrência de um evento do sistema de arquivos (neste exemplo, quando os arquivos no diretório forem gravados). Nesse ponto, ele sai com sucesso e o corpo do loop é executado. Em seguida, volta a esperar novamente. Como o inotifywaitcomando aguarda que algo aconteça no diretório, é muito mais eficiente do que pesquisar continuamente o diretório.

Stuart Caie
fonte
`(apt-get ou yum) instala o inotify-tools` +1 cool!
JJoao
4

Mova o while 1 para o script perl (seguindo a sugestão do @roaima)

#!/usr/bin/perl

 use Linux::Inotify2;

 my $inotify = new Linux::Inotify2 or die "unable to inotify: $!";

 $inotify->watch ("Dir", IN_MODIFY, ## or in_{acess,create,open, etc...}
   sub { my $e = shift;
     my $name = $e->fullname;
     ## whatever 
     print "$name was modified\n" if $e->IN_MODIFY;
  });

 1 while $inotify->poll;
JJoao
fonte
1
Isso não atende ao requisito de reinicialização, se o script travar ou for morto por algum motivo.
Jlliagre
@ jlliagre, obrigado pelo comentário. Na resposta, não vejo uma especificação clara para o comportamento pretendido após uma falha ou morte. Esta é claramente uma pergunta interessante e relevante. Por enquanto eu vou mantê-lo simples (se o script morreu ou foi morto, ficar :) mortos
JJoao
@ jlliagre É para isso que servem as exceções. Mas Perl pode não ser a escolha mais apropriada nesse caso, pois não suporta o mecanismo de exceção. Apenas um palpite. Seria melhor portar o script para um idioma que suporte exceções. Tudo depende do tamanho do trabalho de portabilidade, é claro.
@ Nasha, em Perl, com manipuladores de sinal, variáveis ​​de erro, Tyr :: Tiny, etc, podemos fazer isso! ... mas - Eu odeio o tratamento de exceção e recuperação de erros: eles nunca estão completos ...
JJoao
1
@JJoao Concordo com você no fato de que a recuperação de erros raramente é completa. É claro que cabe ao desenvolvedor cobrir todos os casos possíveis. O mesmo acontece com os CLPs no mundo da indústria, portanto, deve ser bem possível, afinal ;-).
3

Quando seu script perl se destina a continuar em execução o tempo todo, por que usar a construção while? Quando o perl falha devido a algum problema sério, o novo script perl iniciado por enquanto pode travar com a mesma força. De novo e de novo e assim por diante.
se você realmente deseja que seu perl seja reiniciado, considere o crontab e um script que primeiro verifique se há instâncias em execução. Dessa forma, seu script será iniciado após uma reinicialização.

Walter A
fonte
2

Em geral, não há nenhum problema em usar, while truepois é um teste minúsculo que só é executado após o término do script perl. Lembre-se de que, dependendo da variante do Linux / Unix que você está usando, o script pode ser encerrado no logoff. Nesse caso, considere usar o loop em um script e chamá-lo nohupe colocá-lo em segundo plano, ou seja,nohup myscript &

Se o script perl terminar com muita frequência e causar carga na CPU, essa carga será responsável pelo script perl e não pelo while true.

Veja também man nohuppara detalhes.

Lambert
fonte
O único problema é o potencial para um hot loop que aumenta a utilização da CPU. Então, eu concordo totalmente em fazer uma chamada de suspensão dentro do loop para domar.
Craig
2

Se você deseja gerenciar um processo, consulte um gerente de processo para fazê-lo.

Com muitos sistemas recentes, você pode usar o systemd para monitorar seus processos (que é um dos benefícios do systemd sobre os scripts de inicialização clássicos). Se a sua distro de escolha não utiliza systemd, você poderia usar daemontools ou monit .

Andrew Aylett
fonte
moné uma alternativa simples, se a imensidão da monit e do sistema incomodá-lo tanto quanto eles.
Anko
2

O whileloop não é realmente uma boa ideia. Não há como escapar lá - apenas corre para sempre - estaticamente . Muitas coisas podem mudar no ambiente e não serão afetadas - e isso pode ser ruim.

Por exemplo, se o executável do shell responsável por esse whileloop for atualizado, o kernel não poderá liberar espaço em disco para a versão antiga até que o script seja encerrado porque ele precisa manter o descritor enquanto for executado. E isso é verdade para qualquer arquivo que o shell possa ter aberto por qualquer motivo para executar o loop - todos eles serão mantidos abertos por esse whileloop pelo tempo que for executado - para sempre.

E se, pro céu, haja um vazamento de memória em qualquer lugar do shell executando esse loop - mesmo o mais minucioso - ele simplesmente continuará vazando - estaticamente . Ele será construído sem controle e o único recurso é matá-lo à força, e iniciá-lo novamente para fazer o mesmo mais tarde.

Realmente não é assim que você deve configurar um processo em segundo plano - pelo menos não na minha opinião. Em vez disso, como penso, deve haver um ponto de redefinição - uma atualização do script e seu estado. A maneira mais fácil de fazer isso é com exec. Você pode substituir o processo atual por um novo - mantendo o mesmo PID - mas ainda executando um novo processo.

Por exemplo, se seu perlscript retornar true depois de ter tratado com êxito uma modificação de arquivo em algum diretório monitorado:

#!/bin/sh
trap 'rm -rf -- "${ldir%%*.}"' 0 INT
_exec() case    $#      in
        (0)     exec env -  "PID=$$" "ldir=${TMPDIR:-/tmp}/." \
                            "$0" "$@";;
        (*)     export "$@" "boff=0" "lmt=30"
                exec        "$0" "$@";;
        esac
[ "$PID" = "$$" ] || _exec
[ -w "$ldir" ]  &&
case    $ldir   in
(*.)    until   mkdir -- "$ldir"
        do      :& ldir=$ldir$$$!
        done    2>/dev/null
;;
(*)     until   /someperlscript.pl ||
                [ "$((boff+=1))"  -ge "$lmt" ]
        do      [ -d "$ldir" ]     &&
                sleep "$boff"      || ! break
        done
;;esac  &&      _exec ldir PID

...ou algo assim. Algo que permite que o mecanismo básico por trás do loop seja atualizado de vez em quando.

mikeserv
fonte
1

Votei algumas dessas respostas, mas instintivamente tenho o maior carinho pela resposta da @ WalterA. Bem, fiz até criar minha própria resposta ...

Pessoalmente, eu tenderia a atualizar o script Perl para que ele escreva uma entrada de log descritiva sobre a falha e envie um alerta para um administrador.

Se o script Perl deve continuar em execução, aguardando eventos de alteração no sistema de arquivos, por que está falhando?

Se não estiver falhando, por que você está preocupado em envolvê-lo em um script para reiniciá-lo infinitamente?

Se houver um problema de configuração ou alguma dependência interrompida que faça com que o script Perl seja interrompido, simplesmente reiniciá-lo repetidamente não fará com que ele repentinamente comece a funcionar.

Você conhece a definição de insanidade, certo? (fazendo a mesma coisa repetidamente, esperando um resultado diferente). Apenas dizendo. ;-)

Craig
fonte
haha- eu sei, eu sei. mas você sabe - o mundo real é uma merda. De qualquer forma - o motivo é que ele processa arquivos, e alguns arquivos podem nem ser transmitidos corretamente. Ainda não sabemos a variedade de razões pelas quais isso pode falhar. Eu vejo o seu ponto sobre o log de erro, mas eu ainda vai precisar dele backup para processar o próximo arquivo via algo como supervisord
Abhinav Gujjar
Se você conseguir capturar as exceções, poderá registrá-las, continuar processando e até voltar e, ocasionalmente, tentar novamente os arquivos com falha? Talvez o arquivo não foi bloqueado por outro usuário ou processo quando você tentou processá-lo, etc.
Craig