Вопрос или проблема
#!/bin/bash
( sleep 10; echo foo; )
echo bar
- начать запускать код
- в течение ожидания отправить SIGINT (Ctrl+C)
- есть ли шанс когда-либо увидеть “bar” в выводе?
Обоснование:
()
создает подпроцесс, который первым обрабатывает сигнал, поэтому основной процесс bash видит, что команда завершилась, и выполняет следующую строку кода, прежде чем успеть обработать SIGINT.
Вариация:
#!/bin/bash
( sleep 10; echo foo; ) &
wait -f
echo bar
Я не видел этого и не считаю это возможной ошибкой и т. д., я просто хочу понять, как это должно работать?
Я думаю, что SIGINT передается от родительского процесса к дочернему, и поэтому основной процесс bash должен быть осведомлен о сигнале к моменту возврата подпроцесса, так что echo bar
никогда не выполняется, но это всего лишь то, что я ожидаю, и возможно, это не то, что происходит на самом деле.
(версия bash 5)
Ответ или решение
В рассматриваемом фрагменте кода действительно существует потенциальная угроза состояния гонки (race condition), связанная с обработкой сигналов в Bash. Давайте рассмотрим оба случая подробнее.
Первый пример:
#!/bin/bash
( sleep 10; echo foo; )
echo bar
- При запуске данного скрипта создается подпроцесс, который будет спать в течение 10 секунд и затем напечатает "foo".
- Если во время выполнения
sleep
вы отправите сигналSIGINT
(например, нажав Ctrl+C), Bash впервые увидит, что его родительский процесс был прерван. - При этом подпроцесс не будет немедленно завершён. Bash должен обработать сигнал и только затем завершить выполнение команд. Однако, так как главный процесс Bash продолжит обрабатывать команды после завершения подпроцесса, существует вероятность, что он успеет выполнить
echo bar
до того, как потоки обработки сигнала завершатся.
Таким образом, да, теоретически есть вероятность, что вы увидите "bar" в выводе, даже если процесс родительского скрипта был прерван.
Второй пример:
#!/bin/bash
( sleep 10; echo foo; ) &
wait -f
echo bar
В этом случае:
- Мы запускаем подпроцесс в фоновом режиме (с использованием
&
), поэтому он будет работать независимо от главного скрипта. - Команда
wait -f
заставляет основной скрипт ожидать завершения всех фоновых процессов (в нашем случае только одного подпроцесса). - Если вы снова отправите
SIGINT
во время ожидания, сигнал будет передан как главному процессу, так и его подпроцессам. Однако так как основной скрипт будет ожидать завершения подпроцесса, командаecho bar
не будет выполнена, если только не произойдет что-то совершенно неординарное.
Общий вывод:
При обработке сигналов в Bash имеет значение порядок обработки. SIGINT может вызвать завершение исполнения скрипта в любом его состоянии, и это ведет к риску несогласованности вывода, как в заключительном выводе кода, так и в сценариях, когда сигналы обрабатываются. На практике, возможно, вы увидите "bar", особенно если управление сигналами осуществляется асинхронно, и это может произойти в зависимости от конкретных временных рамок и состояния системы.
Таким образом, важно помнить, что сигнал может быть обработан в момент, когда он достигнет соответствующего процесса, и в случае с фоновыми задачами всегда есть вероятность наблюдать неожиданный вывод.