Можно ли использовать rm для синхронизации проверок файлов блокировки? - PullRequest
4 голосов
/ 15 июня 2019

Я пытался улучшить кусок шелл-кода, реализуя сломанный механизм блокировки.

Моя идея состояла в том, чтобы пропустить синхронизацию только одному вызывающему, вызывая rm для файла.

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

echo $$ > $PIDFILE
# this should succeed only for one process
rm $flag || exit
echo $$ > $PIDFILE

Я сделал несколько одновременных звонков и бросил свой мозг против него и не столкнулся с ошибкой.

Но это на самом деле безопасно?

Ответы [ 2 ]

5 голосов
/ 15 июня 2019

Это небезопасно.

Предположим, что три копии вашего сценария (A, B и C) запускаются одновременно, а /tmp/test.pid изначально не существует.

Пусть A и B завершатначальные операторы скрипта:

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

Переключитесь на A и дайте ему выполнить еще два оператора:

echo $$ > $PIDFILE
rm $flag || exit

Это успешно;$PIDFILE теперь содержит PID A.

Переключитесь на B и позвольте ему выполнять те же операторы.rm не удается, и поэтому B завершается, но $PIDFILE теперь содержит PID B.

Переключение на C. C только что начал работать, поэтому первое, что он делает, это воссоздает $flag:

PIDFILE=/tmp/test.pid

flag=$PIDFILE.flag
touch $flag

Теперь идет проверка PID:

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

Это проходит, потому что $PIDFILE содержит PID B, но B. больше не работает.

Теперь мы переходим к

echo $$ > $PIDFILE
rm $flag || exit

Это также проходит, потому что C только что воссоздал файл $flag.

Теперь у нас есть и A, и C, бегущие друг с другом, чтобы снова перезаписать $PIDFILE.


Кроме того, существует также проблема "ложного срабатывания":

if [ -f $PIDFILE ]; then
  ps | grep -qE '^\s*'$(cat $PIDFILE) && exit
fi

Возможно, у вас устаревший $PIDFILE, но содержащийся в нем PID был повторно использован для другого процесса.В этом случае вы получаете не гонку (и слишком много экземпляров вашего скрипта), а отказ в обслуживании (слишком мало экземпляров вашего скрипта: 0).Ваш скрипт увидит запущенный процесс, который, как оказалось, имеет «неправильный» PID, и завершится.

1 голос
/ 15 июня 2019

Ваш код небезопасен, но, возможно, не по той причине, о которой вы думаете.

Вот одна из возможных последовательностей, которая может позволить процессу пройти проверку, пока еще выполняется другая:

  1. Два процесса A и B запускают скрипт и заканчиваются в точке, предшествующей первой echo $$ > $PIDFILE одновременно.По какой-то причине процесс B здесь кратковременно останавливается.
  2. Процесс A записывает свой PID в файл pid, успешно отсоединяет файл флага, снова записывает свой PID в файл pid снова и продолжает работувыполняется.
  3. Теперь процесс B возобновляет работу.Он записывает свой PID в pidfile, перезаписывает его, затем не может отсоединить файл флага и завершает работу.Процесс A все еще выполняется, но теперь pid-файл больше не содержит свой PID!
  4. Теперь процесс C запускается, воссоздает файл флага, отмечает, что pid-файл существует, но содержит PID процесса B (которого больше нет) и, таким образом, успешно проходит все проверки.

Существуют и другие возможные последовательности событий, приводящих к одинаковому результату.

...