Prześlij SIGTERM do dziecka w Bash

86

Mam skrypt Bash, który wygląda podobnie do tego:

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

Teraz, jeśli powłoka bash uruchamiająca skrypt odbierze sygnał SIGTERM, powinna również wysłać SIGTERM do działającego serwera (który blokuje, więc pułapka nie jest możliwa). Czy to jest możliwe?

Lorenz
źródło

Odpowiedzi:

91

Próbować:

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

Zwykle bashignoruje wszelkie sygnały podczas wykonywania procesu potomnego. Uruchomienie serwera &spowoduje umieszczenie go w tle w systemie kontroli zadań powłoki, z $!zachowaniem PID serwera (do użycia z waiti kill). Wywołanie waitbędzie wtedy czekać na zakończenie zadania z określonym PID (serwerem) lub na wyzwolenie sygnałów .

Gdy powłoka odbierze SIGTERM(lub serwer wyjdzie niezależnie), waitpołączenie zostanie zwrócone (wyjście z kodem wyjścia serwera lub z numerem sygnału + 128 w przypadku odebrania sygnału). Następnie, jeśli powłoka otrzymała SIGTERM, wywoła _termfunkcję określoną jako moduł obsługi pułapki SIGTERM przed wyjściem (w którym przeprowadzamy czyszczenie i ręcznie propagujemy sygnał do procesu serwera za pomocą kill).

Cuonglm
źródło
Wygląda dobrze! Spróbuję i odpowiem, kiedy to przetestuję.
Lorenz
7
Ale exec zastępuje powłokę danym programem , nie jestem pewien, dlaczego kolejne waitwywołanie jest potrzebne?
iruvar
5
Myślę, że punkt 1_CR jest poprawny. Albo po prostu używasz exec /bin/start/main/server --nodaemon(w takim przypadku proces powłoki jest zastępowany procesem serwera i nie musisz propagować żadnych sygnałów), albo używasz /bin/start/main/server --nodaemon &, ale wtedy execnie ma to naprawdę znaczenia.
Andreas Veithen
2
Jeśli chcesz, aby skrypt powłoki zakończył się dopiero po zakończeniu potomka, w _term()funkcji powinieneś wait "$child"ponownie. Może to być konieczne, jeśli jakiś inny proces nadzorujący czeka na śmierć skryptu powłoki przed ponownym uruchomieniem go ponownie, lub jeśli zostałeś uwięziony w EXITcelu wykonania czyszczenia i konieczne jest uruchomienie go dopiero po zakończeniu procesu potomnego.
LeoRochael
1
@AlexanderMills Przeczytaj pozostałe odpowiedzi. Albo szukasz exec, albo chcesz ustawić pułapki .
Stuart P. Bentley,
78

Bash nie przekazuje sygnałów takich jak SIGTERM do procesów, na które obecnie czeka. Jeśli chcesz zakończyć swój skrypt, dzieląc się na serwer (pozwalając mu obsługiwać sygnały i cokolwiek innego, tak jakbyś uruchomił serwer bezpośrednio), powinieneś użyć exec, który zastąpi powłokę z otwartym procesem :

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

Jeśli trzeba zachować skorupę wokół jakiegoś powodu (tj. Trzeba zrobić porządki po serwer kończy), należy użyć kombinacji trap, waiti kill. Zobacz odpowiedź SensorSmith .

Stuart P. Bentley
źródło
To poprawna odpowiedź! O wiele bardziej zwięzłe i dotyczy oryginalnych zapytań OP
BrDaHa
20

Andreas Veithen zwraca uwagę, że jeśli nie musisz wracać z połączenia (jak w przykładzie PO), wystarczy zadzwonić za pomocą execpolecenia (odpowiedź @Stuart P. Bentley ). W przeciwnym razie „tradycyjny” trap 'kill $CHILDPID' TERM(odpowiedź @ cuonglm) jest początkiem, ale waitwywołanie faktycznie powraca po uruchomieniu procedury obsługi pułapki, co może być jeszcze przed zakończeniem procesu potomnego. Dlatego waitwskazane jest „dodatkowe” połączenie z (odpowiedź @ user1463361 ).

Chociaż jest to poprawa, nadal ma warunek wyścigu, co oznacza, że ​​proces może nigdy nie zostać zakończony (chyba że sygnalizator spróbuje wysłać sygnał TERM). Okno podatności polega na zarejestrowaniu modułu obsługi pułapki i zarejestrowaniu PID dziecka.

Poniżej opisano tę lukę (zawartą w funkcjach do ponownego użycia).

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
źródło
2
Doskonała robota - zaktualizowałem link w mojej odpowiedzi, aby wskazał tutaj (poza tym, że jest to bardziej kompleksowe rozwiązanie, nadal jestem trochę zirytowany, że interfejs StackExchange nie uznaje mnie w odpowiedzi cuonglm za naprawienie skryptu właściwie robię, co powinien i pisząc prawie cały tekst wyjaśniający po OP, który nawet nie rozumiał, dokonał kilku drobnych przeróbek).
Stuart P. Bentley,
2
@ StuartP. Bentley, dzięki. I był zaskoczony montaż wymaga to dwa (nie akceptowane) odpowiedzi i zewnętrznego odniesienia, a potem musiałem zaniedbany stan wyścigu. Ulepszę moje referencje do linków, co będę mógł trochę dodać.
SensorSmith
3

Podane rozwiązanie nie działa dla mnie, ponieważ proces został zabity przed faktycznym zakończeniem polecenia czekania. Znalazłem ten artykuł http://veithen.github.io/2014/11/16/sigterm-propagation.html , ostatni fragment kodu działa dobrze w moim przypadku aplikacji uruchomionej w OpenShift z niestandardowym programem uruchamiającym sh. Skrypt sh jest wymagany, ponieważ muszę mieć możliwość zrzutów wątków, co jest niemożliwe w przypadku, gdy PID procesu Java wynosi 1.

trap 'kill -TERM $PID' TERM INT
$JAVA_EXECUTABLE $JAVA_ARGS &
PID=$!
wait $PID
trap - TERM INT
wait $PID
EXIT_STATUS=$?
użytkownik1463361
źródło