Leiten Sie SIGTERM an das Kind in Bash weiter

86

Ich habe ein Bash-Skript, das ungefähr so ​​aussieht:

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

Wenn die Bash-Shell, in der das Skript ausgeführt wird, ein SIGTERM-Signal empfängt, sollte sie auch ein SIGTERM-Signal an den ausgeführten Server senden (das blockiert, sodass keine Überfüllung möglich ist). Ist das möglich?

Lorenz
quelle

Antworten:

91

Versuchen:

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

Normalerweise bashwerden alle Signale ignoriert, während ein untergeordneter Prozess ausgeführt wird. Wenn Sie den Server mit starten, &wird er im Hintergrund in das Jobsteuerungssystem der Shell eingefügt, wobei $!die PID des Servers (zur Verwendung mit waitund kill) festgehalten wird . Der Anruf waitwartet dann auf den Abschluss des Auftrags mit der angegebenen PID (dem Server) oder auf das Auslösen von Signalen .

Wenn die Shell empfängt SIGTERM(oder der Server unabhängig beendet), waitkehrt der Anruf zurück (beendet mit dem Beendigungscode des Servers oder mit der Signalnummer + 128, falls ein Signal empfangen wurde). Wenn die Shell anschließend SIGTERM empfangen hat, ruft sie _termvor dem Beenden die als SIGTERM-Trap-Handler angegebene Funktion auf (in der das Signal bereinigt und manuell an den Serverprozess weitergegeben wird kill).

cuonglm
quelle
Sieht gut aus! Ich werde es versuchen und antworten, wenn ich es getestet habe.
Lorenz
7
Aber exec ersetzt die Shell durch das angegebene Programm , mir ist nicht klar, warum der nachfolgende waitAufruf dann benötigt wird?
Iruvar
5
Ich denke, dass der Punkt von 1_CR gültig ist. Entweder verwenden Sie einfach exec /bin/start/main/server --nodaemon(in diesem Fall wird der Shell-Prozess durch den Server-Prozess ersetzt und Sie müssen keine Signale weitergeben), oder Sie verwenden /bin/start/main/server --nodaemon &, aber dies execist nicht wirklich sinnvoll.
Andreas Veithen
2
Wenn Sie möchten, dass Ihr Shell-Skript erst beendet wird, nachdem das untergeordnete Skript beendet wurde, müssen Sie es in der _term()Funktion wait "$child"erneut ausführen. Dies kann erforderlich sein, wenn ein anderer überwachender Prozess darauf wartet, dass das Shell-Skript EXITabstürzt, bevor es erneut gestartet wird , oder wenn Sie auch eine Bereinigung durchführen und es erst ausführen müssen, nachdem der untergeordnete Prozess abgeschlossen wurde.
LeoRochael
1
@AlexanderMills Lesen Sie die anderen Antworten. Entweder Sie suchen execoder Sie möchten Fallen aufstellen .
Stuart P. Bentley
78

Bash leitet Signale wie SIGTERM nicht an Prozesse weiter, auf die es gerade wartet. Wenn Sie Ihren Skript beenden wollen , indem in segueing Ihren Server (so dass es Signale und etwas anderes zu behandeln, als ob Sie den Server direkt begonnen hatten), sollten Sie verwenden exec, das wird die Schale ersetzen mit dem Prozess geöffnet wird :

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

Wenn Sie die Schale um aus irgendeinem Grund halten müssen (dh. Sie müssen eine gewisse Bereinigung tun , nachdem der Server beendet), sollten Sie eine Kombination aus verwenden trap, waitund kill. Siehe die Antwort von SensorSmith .

Stuart P. Bentley
quelle
Das ist die richtige Antwort! So viel prägnanter und adressiert OPs Originalfrage genau
BrDaHa
20

Andreas Veithen weist darauf hin, dass, wenn Sie nicht vom Anruf zurückkehren müssen (wie im Beispiel des OP), ein einfacher Aufruf über den execBefehl ausreicht ( Antwort von @Stuart P. Bentley ). Andernfalls ist "traditionell" trap 'kill $CHILDPID' TERM(@ cuonglms Antwort) ein Start, aber der waitAufruf kehrt tatsächlich zurück, nachdem der Trap-Handler ausgeführt wurde, was noch möglich ist, bevor der untergeordnete Prozess tatsächlich beendet wird. Daher ist ein "zusätzlicher" Anruf an waitratsam ( Antwort von @ user1463361 ).

Dies ist zwar eine Verbesserung, es besteht jedoch immer noch eine Race-Bedingung, was bedeutet, dass der Prozess möglicherweise nie beendet wird (es sei denn, der Signalgeber versucht erneut, das TERM-Signal zu senden). Das Fenster der Sicherheitsanfälligkeit liegt zwischen der Registrierung des Trap-Handlers und der Aufzeichnung der PID des Kindes.

Das Folgende beseitigt diese Sicherheitsanfälligkeit (in Funktionen zur Wiederverwendung verpackt).

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
quelle
2
Hervorragende Arbeit - Ich habe den Link in meiner Antwort aktualisiert, um auf diesen Punkt zu verweisen (obgleich dies eine umfassendere Lösung ist, ärgere ich mich immer noch ein wenig darüber, dass die StackExchange-Benutzeroberfläche mir die Antwort von cuonglm für das Korrigieren des Skripts nicht zu verdanken hat tatsächlich das tun, was es soll, und so ziemlich den gesamten erläuternden Text nach dem OP schreiben, der nicht einmal verstanden hat, und ein paar kleinere Änderungen vorgenommen hat).
Stuart P. Bentley
2
@ StuartP.Bentley, danke. Ich war überrascht, als ich zwei (nicht akzeptierte) Antworten und einen externen Verweis zusammenstellte, und musste dann die Rennbedingungen durchgehen. Ich werde meine Verweise auf Links aktualisieren, um so wenig zusätzlichen Kudos wie möglich zu erhalten.
SensorSmith
3

Die bereitgestellte Lösung funktioniert bei mir nicht, da der Prozess abgebrochen wurde, bevor der Wartebefehl tatsächlich beendet wurde. Ich fand diesen Artikel http://veithen.github.io/2014/11/16/sigterm-propagation.html , der letzte Schnipsel funktionierte gut in meinem Anwendungsfall, der in OpenShift mit Custom Sh Runner gestartet wurde. Das sh-Skript ist erforderlich, da ich die Fähigkeit haben muss, Thread-Dumps abzurufen, was unmöglich ist, wenn die PID des Java-Prozesses 1 ist.

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