ПИД файлы все еще испорчены, когда делаете это "правильно"?

Перезапуск службы часто реализуется через PID файл - I.e. идентификатор процесса записывается в некоторый файл и на основе этого номера команда останова будет убивать процесс (или до перезапуска).

Когда вы думаете об этом (или если вам это не нравится, поиск), вы обнаружите, что это проблематично, поскольку каждый PID может быть повторно использован. Представьте себе полный перезапуск сервера, когда вы вызываете "./your- script.sh start" при запуске (например, @reboot в crontab). Теперь ваш script.sh будет убивать произвольный PID, потому что он сохранил PID из живого до перезагрузки.

Обходное решение, которое я могу себе представить, это хранить дополнительную информацию, чтобы вы могли делать "ps -pid | grep ', и только если это вернет что-то, что вы его убьете. Или есть лучшие варианты с точки зрения надежности и/или простоты?

#!/bin/bash

function start() {
  nohub java -jar somejar.jar >> file.log 2>&1 &
  PID=$!
  # one could even store the "ps -$PID" information but this makes the
  # killing too specific e.g. if some arguments will be added or similar
  echo "$PID somejar.jar" > $PID_FILE
}

function stop() {
  if [[ -f "$PID_FILE" ]]; then
    PID=$(cut -f1 -d' ' $PID_FILE)
    # now get the second information and grep the process list with this
    PID_INFO=$(cut -f2 -d' ' $PID_FILE)
    RES=$(ps -$PID | grep $PID_INFO)
    if [[ "x$RES" != "x" ]]; then
       kill $PID
    fi
  fi
}

Ответ 1

Проблема с файлами PID многогранна, не ограничивается только переработкой и перезагрузкой.

Большая проблема заключается в том, что между информацией в файле PID и состоянием процесса существует неизбежное отключение/расхождение.

Это поток использования файлов PID:

  • Вы используете fork и выполняете процесс. "Родительский" процесс знает PID вилки и имеет гарантии, что этот PID зарезервирован исключительно для его вилки.
  • Ваш родитель записывает PID вилки в файл.
  • Ваш родитель умирает вместе с ним и гарантирует эксклюзивность PID.
  • Другой процесс считывает число в PID файле.
  • Другой процесс проверяет, есть ли процесс в системе с тем же PID, что и тот, который он читал.
  • Другой процесс отправляет сигнал процессу с прочитанным PID.

В (1) все прекрасно и денди. У нас есть PID, и ядро ​​гарантирует, что это число зарезервировано для нашего предполагаемого процесса.

В (2) вы получаете контроль над PID для других процессов, которые не имеют этой гарантии. Сама по себе не проблема, но такой акт редко бывает когда-либо без ошибок.

В (3) ваш родительский процесс умирает. Только в нем была гарантия ядра на эксклюзивность PID. Он может или не мог сделать ожидание (2) на PID. Истинное состояние предполагаемого процесса теряется, все, что у нас осталось, - это идентификатор в PID файле, который может или не может ссылаться на предполагаемый процесс.

В (4) процесс без каких-либо гарантий считывает PID файл, любое использование этого числа имеет только произвольный успех.

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

В (6) мы совершаем худшую ошибку: мы на самом деле выполняем мутирующее действие, направленное на то, чтобы повлиять на начальный процесс, но никоим образом не гарантируя этого намерения. Мы могли бы сигнализировать о любом случайном системном процессе.

Почему это? Какого рода вещи могут случиться с PID?

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

Как только родитель освобождает свою гарантию или после (3), ядро ​​перерабатывает PID мертвого процесса. Зомби ушел, и теперь PID теперь может использоваться любым другим новым процессом, который разветвляется. Скажем, вы компилируете что-то, тысячи мелких процессов порождаются. Ядро выбирает случайные или последовательные (в зависимости от его конфигурации) новые PID для каждого. Вы закончили, теперь вы перезапускаете apache. Ядро повторно использует освобожденный PID вашего мертвого процесса для чего-то важного.

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

Любое действие (5) (6), которое вы берете с прочитанным вами номером, будет нацелено на новый процесс, а не на старый.

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

Мораль истории: НЕ ДАЙТЕ PID ваших детей кому-либо еще. Родитель и только родитель должны использовать его, потому что он единственный в системе (за исключением ядра) с любыми гарантиями на его существование и идентичность.

Обычно это означает, что родитель жив и вместо того, чтобы сигнализировать что-то, чтобы завершить процесс, вместо этого поговорить с родителем; с помощью гнезд или тому подобного. См. http://smarden.org/runit/ и др.

Ответ 2

В качестве альтернативы runit есть команда daemon из библиотеки libslack, которая может автоматически повторно запускать клиентскую программу при ее завершении - без использования файла PID.

Использование именованного демона с помощью команды daemon позволяет вручную перезапустить клиентскую программу; это, однако, создаст файл PID, который может привести к условиям гонки, как уже указывалось lhunath.

# daemon example without PID file
daemon --respawn --acceptable=10 --delay=10 bash -- -c 'sleep 30'

# from: man daemon
# "If started with the --respawn option, the client process 
# will be restarted after it is killed by the SIGTERM signal."
#
# (Problem would be to reliably get e.g. the bash pid in the daemon example above.)