Transférer SIGTERM à l'enfant dans Bash

86

J'ai un script Bash, qui ressemble à ceci:

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

Maintenant, si le shell bash exécutant le script reçoit un signal SIGTERM, il doit également envoyer un SIGTERM au serveur en cours d’exécution (ce qui bloque, donc aucune interruption possible). Est-ce possible?

Lorenz
la source

Réponses:

91

Essayer:

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

Normalement, bashignorera tous les signaux pendant l'exécution d'un processus enfant. Le démarrage du serveur avec &sera intégré au système de contrôle des tâches du shell, en $!tenant le PID du serveur (à utiliser avec waitet kill). L'appel waitattendra ensuite que le travail avec le PID spécifié (le serveur) soit terminé ou que tous les signaux soient déclenchés .

Lorsque le shell reçoit SIGTERM(ou le serveur quitte indépendamment), l' waitappel est renvoyé (avec le code de sortie du serveur ou le numéro de signal + 128 en cas de réception d'un signal). Par la suite, si le shell a reçu SIGTERM, il appellera la _termfonction spécifiée en tant que gestionnaire d’interruptions SIGTERM avant de quitter (dans lequel nous effectuons tout nettoyage et propagons manuellement le signal au processus serveur à l’aide de kill).

cuonglm
la source
Cela semble bon! Je vais l'essayer et répondre quand je l'ai testé.
Lorenz
7
Mais exec remplace le shell avec le programme donné , je ne comprends pas pourquoi l' waitappel suivant est alors nécessaire?
iruvar
5
Je pense que le point de 1_CR est valide. Soit vous utilisez simplement exec /bin/start/main/server --nodaemon(dans ce cas, le processus shell est remplacé par le processus serveur et vous n’avez pas besoin de propager de signaux), soit vous utilisez /bin/start/main/server --nodaemon &, mais cela execn’a pas vraiment de sens.
Andreas Veithen
2
Si vous voulez que votre script shell se termine uniquement après la fin de l'enfant, _term()vous devriez le faire à wait "$child"nouveau dans la fonction . Cela peut être nécessaire si un autre processus de supervision attend la fin du script shell avant de le redémarrer à nouveau, ou si vous êtes également bloqué EXITpour effectuer un nettoyage et si vous souhaitez qu'il s'exécute uniquement une fois le processus enfant terminé.
LeoRochael
1
@AlexanderMills Lisez les autres réponses. Vous cherchez execou vous voulez installer des pièges .
Stuart P. Bentley
78

Bash ne transmet pas de signaux tels que SIGTERM aux processus qu’il attend actuellement. Si vous voulez terminer votre script en vous connectant à votre serveur (en lui permettant de gérer les signaux et tout le reste, comme si vous aviez démarré le serveur directement), vous devriez utiliser exec, qui remplacera le shell par le processus en cours d'ouverture :

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

Si vous avez besoin de garder la coquille autour pour une raison quelconque ( par exemple. Vous devez faire un nettoyage après que le serveur se termine), vous devez utiliser une combinaison de trap, waitet kill. Voir la réponse de SensorSmith .

Stuart P. Bentley
la source
C'est la bonne réponse! Tellement plus concis et répond exactement à la demande initiale de OP
BrDaHa
20

Andreas Veithen souligne que si vous n'avez pas besoin de revenir de l'appel (comme dans l'exemple du PO), un simple appel à l'aide de la execcommande suffit ( réponse de @Stuart P. Bentley ). Sinon, le "traditionnel" trap 'kill $CHILDPID' TERM(réponse de @ cuonglm) est un début, mais l' waitappel revient en réalité après l'exécution du gestionnaire d'interruptions, ce qui peut être encore avant la fin du processus enfant. Il waitest donc conseillé de faire un "extra" appel ( réponse de @ user1463361 ).

Bien qu'il s'agisse d'une amélioration, il existe toujours une condition de concurrence critique, ce qui signifie que le processus ne peut jamais se terminer (à moins que le signaleur ne tente d'envoyer le signal TERM). La fenêtre de vulnérabilité se situe entre l'enregistrement du gestionnaire d'interruptions et l'enregistrement du PID de l'enfant.

Ce qui suit élimine cette vulnérabilité (intégrée dans des fonctions pour la réutilisation).

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
la source
2
Excellent travail - j'ai mis à jour le lien dans ma réponse afin de l'indiquer ici (en plus de cela étant une solution plus complète, je suis toujours un peu contrarié que l'interface utilisateur de StackExchange ne me crédite pas dans la réponse de cuonglm pour avoir corrigé le script à fait ce qu’il est censé faire et écrire à peu près tout le texte explicatif après le PO qui ne comprenait même pas, a fait quelques rééditions mineures).
Stuart P. Bentley
2
@ StuartP.Bentley, merci. J'ai été surpris d'assembler cette opération, il a fallu deux réponses (non acceptées) et une référence externe, puis j'ai dû réduire la situation de concurrence critique. Je vais mettre à jour mes références vers des liens en guise de félicitations supplémentaires.
SensorSmith
3

La solution fournie ne fonctionne pas pour moi car le processus a été tué avant la fin de la commande d'attente. J'ai trouvé cet article http://veithen.github.io/2014/11/16/sigterm-propagation.html , le dernier extrait qui fonctionne bien dans mon cas d'application démarrée dans OpenShift avec un coureur personnalisé. Le script sh est requis car je dois avoir la capacité d'obtenir des vidages de threads, ce qui est impossible dans le cas où le processus PID du processus Java est à 1.

trap 'kill -TERM $PID' TERM INT
$JAVA_EXECUTABLE $JAVA_ARGS &
PID=$!
wait $PID
trap - TERM INT
wait $PID
EXIT_STATUS=$?
utilisateur1463361
la source