Encaminhar SIGTERM para filho no Bash

86

Eu tenho um script Bash, que é semelhante a este:

#!/bin/bash
echo "Doing some initial work....";
/bin/start/main/server --nodaemon

Agora, se o shell bash executando o script recebe um sinal SIGTERM, também deve enviar um SIGTERM para o servidor em execução (que bloqueia, para que nenhuma interceptação seja possível). Isso é possível?

Lorenz
fonte

Respostas:

91

Experimentar:

#!/bin/bash 

_term() { 
  echo "Caught SIGTERM signal!" 
  kill -TERM "$child" 2>/dev/null
}

trap _term SIGTERM

echo "Doing some initial work...";
/bin/start/main/server --nodaemon &

child=$! 
wait "$child"

Normalmente, bashignorará quaisquer sinais enquanto um processo filho estiver em execução. Iniciar o servidor com &colocará em segundo plano no sistema de controle de tarefas do shell, $!mantendo o PID do servidor (a ser usado com waite kill). A chamada waitaguardará a conclusão do trabalho com o PID especificado (o servidor) ou a ativação de qualquer sinal .

Quando o shell recebe SIGTERM(ou o servidor sai independentemente), a waitchamada retornará (saindo com o código de saída do servidor ou com o número do sinal + 128 no caso de um sinal ser recebido). Posteriormente, se o shell receber o SIGTERM, ele chamará a _termfunção especificada como o manipulador de trap SIGTERM antes de sair (na qual fazemos qualquer limpeza e propagamos manualmente o sinal para o processo do servidor usando kill).

cuonglm
fonte
Parece bom! Vou tentar e responder quando o testei.
Lorenz
7
Mas exec substitui o shell pelo programa fornecido , não sei por que a waitchamada subseqüente é necessária?
iruvar 26/07/2014
5
Eu acho que o ponto de 1_CR é válido. Ou você simplesmente usa exec /bin/start/main/server --nodaemon(nesse caso, o processo do shell é substituído pelo processo do servidor e você não precisa propagar nenhum sinal) ou usa /bin/start/main/server --nodaemon &, mas execnão é realmente significativo.
Andreas Veithen 13/11/19
2
Se você deseja que seu script shell seja finalizado somente após o término do filho, na _term()função você deve wait "$child"novamente. Isso pode ser necessário se você tiver algum outro processo de supervisão aguardando a morte do script do shell antes de reiniciá-lo novamente ou se você também tiver interceptado EXITa limpeza e precisar executar apenas após a conclusão do processo filho.
LeoRochael
1
@AlexanderMills Leia as outras respostas. Você está procurando execou deseja criar armadilhas .
Stuart P. Bentley
78

O Bash não encaminha sinais como o SIGTERM para processos nos quais está aguardando. Se você deseja finalizar seu script seguindo para o servidor (permitindo manipular sinais e qualquer outra coisa, como se você tivesse iniciado o servidor diretamente), você deve usar o execque substituirá o shell pelo processo que está sendo aberto :

#!/bin/bash
echo "Doing some initial work....";
exec /bin/start/main/server --nodaemon

Se você precisa manter o shell em torno, por algum motivo (ie. Que você precisa fazer alguma limpeza após o servidor termina), você deve usar uma combinação de trap, waite kill. Veja a resposta do SensorSmith .

Stuart P. Bentley
fonte
Essa é a resposta correta! Muito mais conciso e aborda a pergunta original do OP exatamente
BrDaHa
20

Andreas Veithen ressalta que, se você não precisar retornar da chamada (como no exemplo do OP), basta chamar o execcomando ( a resposta de @Stuart P. Bentley ) é suficiente . Caso contrário, o "tradicional" trap 'kill $CHILDPID' TERM(resposta de @ cuonglm) é um começo, mas a waitchamada realmente retorna depois que o manipulador de trap é executado, o que ainda pode acontecer antes que o processo filho realmente saia. Portanto, waité aconselhável uma chamada "extra" ( resposta do @ user1463361 ).

Embora isso seja uma melhoria, ele ainda possui uma condição de corrida, o que significa que o processo nunca pode sair (a menos que o sinalizador tente novamente enviar o sinal TERM). A janela de vulnerabilidade está entre o registro do manipulador de armadilhas e o registro do PID da criança.

A seguir, elimina essa vulnerabilidade (empacotada em funções para reutilização).

prep_term()
{
    unset term_child_pid
    unset term_kill_needed
    trap 'handle_term' TERM INT
}

handle_term()
{
    if [ "${term_child_pid}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null
    else
        term_kill_needed="yes"
    fi
}

wait_term()
{
    term_child_pid=$!
    if [ "${term_kill_needed}" ]; then
        kill -TERM "${term_child_pid}" 2>/dev/null 
    fi
    wait ${term_child_pid}
    trap - TERM INT
    wait ${term_child_pid}
}

# EXAMPLE USAGE
prep_term
/bin/something &
wait_term
SensorSmith
fonte
2
Excelente trabalho - atualizei o link na minha resposta para apontar aqui (além de ser uma solução mais abrangente, ainda estou um pouco irritado por a interface do usuário do StackExchange não me creditar na resposta do cuonglm para corrigir o script. na verdade, faça o que deveria e escreva praticamente todo o texto explicativo depois do OP, que nem sequer entendeu que fez algumas reedições menores).
Stuart P. Bentley
2
@ StuartP.Bentley, obrigado. I foi surpreendido montar este exigia duas (não aceito) respostas e uma referência externa, e então eu tive que correr para baixo a condição de corrida. Vou atualizar minhas referências aos links como pequenos elogios adicionais que posso dar.
SensorSmith 30/09/18
3

A solução fornecida não funciona para mim porque o processo foi interrompido antes que o comando de espera realmente terminasse. Eu descobri que o artigo http://veithen.github.io/2014/11/16/sigterm-propagation.html , o último snippet funciona bem no meu caso de aplicativo iniciado no OpenShift com o sh runner personalizado. O script sh é necessário porque eu preciso ter a capacidade de obter despejos de encadeamento, o que é impossível caso o processo PID do Java seja 1.

trap 'kill -TERM $PID' TERM INT
$JAVA_EXECUTABLE $JAVA_ARGS &
PID=$!
wait $PID
trap - TERM INT
wait $PID
EXIT_STATUS=$?
user1463361
fonte