PID-файлы все еще несовершенны, когда делают это "правильно"?

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

Когда вы думаете об этом (или если вам это не нравится, а затем ищите), вы обнаружите, что это проблематично, так как каждый PID может быть повторно использован. Представьте себе полный перезапуск сервера, когда вы вызываете './your-script.sh start' при запуске (например, @reboot в crontab). Теперь your-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
}

2 ответа

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

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

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

  1. Вы форк и исполняете процесс. "Родительский" процесс знает PID форка и гарантирует, что этот PID зарезервирован исключительно для его форка.
  2. Ваш родитель записывает PID форка в файл.
  3. Ваш родитель умирает вместе с гарантией эксклюзивности PID.
  4. Другой процесс читает число в файле PID.
  5. Другой процесс проверяет, существует ли в системе процесс с тем же PID, что и тот, который он прочитал.
  6. Другой процесс отправляет сигнал процессу с 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/ и др.

В качестве альтернативы 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.)
Другие вопросы по тегам