Переслать SIGTERM ребенку в Баш

86

У меня есть скрипт Bash, который выглядит примерно так:

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

Теперь, если оболочка bash, выполняющая скрипт, получает сигнал SIGTERM, она также должна отправить SIGTERM на работающий сервер (который блокируется, поэтому перехват не возможен). Это возможно?

Лоренц
источник

Ответы:

91

Пытаться:

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

Обычно bashигнорирует любые сигналы во время выполнения дочернего процесса. Запуск сервера с помощью &фонового перехода в систему управления заданиями оболочки с $!удержанием PID сервера (для использования с waitи kill). waitПосле этого вызов будет ожидать завершения задания с указанным PID (сервером) или запуска любых сигналов .

Когда оболочка получает SIGTERM(или сервер выходит независимо), waitвызов возвращается (выход с кодом выхода сервера или с номером сигнала + 128 в случае, если сигнал был получен). После этого, если оболочка получила SIGTERM, она будет вызывать _termфункцию, указанную в качестве обработчика прерываний SIGTERM перед выходом (в котором мы выполняем любую очистку и вручную распространяем сигнал на серверный процесс, используя kill).

cuonglm
источник
Выглядит неплохо! Я попробую это и отвечу, когда я проверил это.
Лоренц
7
Но exec заменяет оболочку на данную программу , мне не ясно, зачем waitтогда нужен последующий вызов?
iruvar
5
Я думаю, что пункт 1_CR действителен. Либо вы просто используете exec /bin/start/main/server --nodaemon(в этом случае процесс оболочки заменяется серверным процессом, и вам не нужно распространять какие-либо сигналы), либо вы используете /bin/start/main/server --nodaemon &, но execэто не имеет особого смысла.
Андреас Вайтен
2
Если вы хотите, чтобы ваш сценарий оболочки завершался только после завершения дочернего процесса, то в _term()функции вы должны wait "$child"снова. Это может быть необходимо, если у вас есть какой-то другой надзорный процесс, ожидающий смерти сценария оболочки, прежде чем снова его перезапускать, или если вы также оказались EXITв ловушке, чтобы выполнить некоторую очистку и нуждаетесь в его запуске только после завершения дочернего процесса.
LeoRochael
1
@AlexanderMills Читайте другие ответы. Либо вы ищете exec, либо вы хотите установить ловушки .
Стюарт П. Бентли
78

Bash не передает сигналы, подобные SIGTERM, процессам, которые он в данный момент ожидает. Если вы хотите завершить ваш скрипт, перейдя на ваш сервер (позволяя ему обрабатывать сигналы и все остальное, как если бы вы запустили сервер напрямую), вы должны использовать exec, который заменит оболочку с открытым процессом :

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

Если вам нужно сохранить оболочку вокруг какой - то причины (то есть. Вы должны сделать некоторые очистки после завершает сервер), вы должны использовать комбинацию trap, waitи kill. Смотрите ответ SensorSmith .

Стюарт П. Бентли
источник
Это правильный ответ! Намного более краткий и
точный
20

Андреас Вайтен отмечает, что если вам не нужно возвращаться из вызова (как в примере с OP), достаточно просто вызвать execкоманду ( ответ @Stuart P. Bentley ). В противном случае «традиционный» trap 'kill $CHILDPID' TERM(ответ @ cuonglm) является началом, но waitвызов фактически возвращается после выполнения обработчика прерываний, который может быть еще до фактического завершения дочернего процесса. Поэтому waitрекомендуется «дополнительный» вызов ( ответ @ user1463361 ).

Несмотря на то, что это улучшение, у него все еще есть состояние гонки, что означает, что процесс может никогда не завершиться (если сигнализатор не попытается отправить сигнал TERM). Окно уязвимости находится между регистрацией обработчика ловушек и записью PID ребенка.

Следующее устраняет эту уязвимость (упаковано в функции для повторного использования).

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
источник
2
Отличная работа - я обновил ссылку в своем ответе, чтобы указать здесь (кроме того, что это более всеобъемлющее решение, я все еще немного раздражен тем, что пользовательский интерфейс StackExchange не учитывает мне ответ cuonglm за исправление сценария для на самом деле делать то, что он должен, и писать почти весь пояснительный текст после того, как ОП, который даже не понимал, сделал несколько незначительных изменений).
Стюарт П. Бентли,
2
@ StuartP.Bentley, спасибо. Я был удивлен, что для сборки этого потребовалось два (не принятых) ответа и внешняя ссылка, а затем мне пришлось бежать из-за состояния гонки. Я буду модернизировать свои ссылки на ссылки как на то, что я могу дать немного больше признания.
SensorSmith
3

Предоставленное решение не работает для меня, потому что процесс был убит до того, как команда ожидания фактически закончилась. Я обнаружил, что в статье http://veithen.github.io/2014/11/16/sigterm-propagation.html последний фрагмент хорошо работает в моем случае приложения, запущенного в OpenShift с настраиваемым sh runner. Скрипт sh необходим, потому что мне нужна возможность получать дампы потоков, что невозможно в случае, если PID процесса Java равен 1.

trap 'kill -TERM $PID' TERM INT
$JAVA_EXECUTABLE $JAVA_ARGS &
PID=$!
wait $PID
trap - TERM INT
wait $PID
EXIT_STATUS=$?
user1463361
источник