Reenviar SIGTERM al niño en Bash

86

Tengo un script Bash, que se parece a esto:

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

Ahora, si el shell bash que ejecuta el script recibe una señal SIGTERM, también debe enviar un SIGTERM al servidor en ejecución (que bloquea, por lo que no es posible una trampa). ¿Es eso posible?

Lorenz
fuente

Respuestas:

91

Tratar:

#!/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á cualquier señal mientras se ejecuta un proceso secundario. Al iniciar el servidor, se &colocará en segundo plano en el sistema de control de trabajo del shell, con la $!retención del PID del servidor (para ser utilizado con waity kill). Las llamadas waitesperarán a que finalice el trabajo con el PID especificado (el servidor) o que se activen las señales .

Cuando el shell recibe SIGTERM(o el servidor sale independientemente), la waitllamada regresará (saliendo con el código de salida del servidor, o con el número de señal + 128 en caso de que se reciba una señal). Luego, si el shell recibió SIGTERM, llamará a la _termfunción especificada como el controlador de trampa SIGTERM antes de salir (en el que hacemos cualquier limpieza y propagamos manualmente la señal al proceso del servidor usando kill).

Cuonglm
fuente
¡Se ve bien! Lo intentaré y responderé cuando lo haya probado.
Lorenz
77
Pero exec reemplaza el shell con el programa dado , no tengo claro por qué waitse necesita la llamada posterior.
iruvar
55
Creo que el punto de 1_CR es válido. O simplemente lo usa exec /bin/start/main/server --nodaemon(en cuyo caso el proceso de shell se reemplaza con el proceso del servidor y no necesita propagar ninguna señal) o lo usa /bin/start/main/server --nodaemon &, pero execno es realmente significativo.
Andreas Veithen
2
Si desea que su script de shell finalice solo después de que se finalice el elemento secundario, entonces en la _term()función debería hacerlo wait "$child"nuevamente. Esto puede ser necesario si tiene algún otro proceso de supervisión esperando que el script de shell muera antes de reiniciarlo nuevamente, o si también está atrapado EXITpara hacer una limpieza y necesita que se ejecute solo después de que el proceso secundario haya finalizado.
LeoRochael
1
@AlexanderMills Lee las otras respuestas. O estás buscando execo quieres establecer trampas .
Stuart P. Bentley
78

Bash no reenvía señales como SIGTERM a los procesos que está esperando actualmente. Si desea finalizar su secuencia de comandos siguiendo su servidor (permitiéndole manejar señales y cualquier otra cosa, como si hubiera iniciado el servidor directamente), debe usar exec, que reemplazará el shell con el proceso que se está abriendo :

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

Si usted necesita para mantener la cáscara alrededor por alguna razón (es decir. Que tiene que hacer algo de limpieza después de que el servidor termina), se debe utilizar una combinación de trap, waity kill. Ver la respuesta de SensorSmith .

Stuart P. Bentley
fuente
¡Esta es la respuesta correcta! Mucho más conciso y aborda exactamente la pregunta original de OP
BrDaHa
20

Andreas Veithen señala que si no necesita regresar de la llamada (como en el ejemplo del OP), simplemente llamar a través del execcomando es suficiente ( respuesta de @Stuart P. Bentley ). De lo contrario, el "tradicional" trap 'kill $CHILDPID' TERM(la respuesta de @ cuonglm) es un comienzo, pero la waitllamada en realidad regresa después de que se ejecuta el controlador de trampa, que puede ser aún antes de que el proceso secundario salga. Por lo tanto, waitse recomienda una llamada "extra" a ( respuesta de @ user1463361 ).

Si bien esto es una mejora, todavía tiene una condición de carrera, lo que significa que el proceso nunca puede salir (a menos que el señalizador vuelva a intentar enviar la señal TERM). La ventana de vulnerabilidad es entre registrar el controlador de trampa y registrar el PID del niño.

Lo siguiente elimina esa vulnerabilidad (empaquetada en funciones para su reutilización).

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
fuente
2
Excelente trabajo: he actualizado el enlace en mi respuesta para señalar aquí (además de ser una solución más completa, todavía estoy un poco molesto porque la interfaz de usuario de StackExchange no me acredita en la respuesta de cuonglm por corregir el script para en realidad hacer lo que se supone que debe hacer y escribir casi todo el texto explicativo después de que el OP que ni siquiera entendió hizo algunas reediciones menores).
Stuart P. Bentley
2
@ StuartP.Bentley, gracias. Yo estaba sorprendido de montar este requiere dos (no aceptado) respuestas y una referencia externa, y luego tuve que correr por la condición de carrera. Actualizaré mis referencias a enlaces según las pequeñas felicitaciones adicionales que puedo dar.
SensorSmith
3

La solución proporcionada no funciona para mí porque el proceso finalizó antes de que el comando de espera realmente terminara. Encontré que el artículo http://veithen.github.io/2014/11/16/sigterm-propagation.html , el último fragmento funciona bien en mi caso de aplicación, comenzó en OpenShift con sh runner personalizado. Se requiere el script sh porque necesito tener la capacidad de obtener volcados de subprocesos, lo que es imposible en caso de que el PID del proceso Java sea 1.

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