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?

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).


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 .


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

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=$?
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.