Классические файлы блокировки Unix: обнаружение устаревших блокировок без гонки

#unix #language-agnostic #posix

Вопрос:

«Классический файл блокировки Unix», реализованный, например, в liblockfile, представляет собой обычный текстовый файл с взаимно согласованным именем, содержащий десятичный идентификатор процесса, удерживающего блокировку. Программа утверждает блокировку, создавая файл блокировки атомарно (например, с O_EXCL помощью или link ), и освобождает блокировку, удаляя ее.

Некоторые реализации, такие как liblockfile, пытаются обнаружить устаревшие файлы блокировки. Если файл блокировки уже существует, они открывают его, считывают PID и вызывают kill(pid, 0) ; если этот вызов завершается ошибкой ESRCH, они удаляют файл блокировки и повторяют попытку создания. Я считаю, что эта логика сама по себе небезопасна из-за этой трехсторонней гонки:

 process 1      process 2                                 process 3
holds lock
               open(tmp2, WRONLY|CREAT|EXCL) -> fd21     open(tmp3, WRONLY|CREAT|EXCL) -> fd31
               write(fd21, pid2)                         write(fd31, pid3)
               close(fd21)                               close(fd31)
               link(tmp2, lock) -> EEXIST
               open(lock, RDONLY) -> fd22
               read(fd22) -> pid1
               close(fd22)
unlink(lock)
exit(0)
               kill(pid1, 0) -> ESRCH 
                                                         link(tmp3, lock) -> 0
               unlink(lock)
               link(tmp2, lock) -> 0
 

после чего процесс 2 украл блокировку у процесса 3.

Есть ли способ, используя только стандартные API POSIX.1-2008, предотвратить эту и все другие возможные гонки при обнаружении устаревших файлов блокировки? Если это невозможно сделать в POSIX, можно ли это сделать с помощью некоторых относительно распространенных расширений системы?

Комментарии:

1. Используйте эксклюзивную блокировку файлов (установленную с flock() помощью или fcntl() — просто будьте последовательны в том, какой из них используется) вместо того, чтобы полагаться на pid.

2. @Shawn Если вы можете объяснить, как использовать flock или fcntl блокировать для исправления условий гонки в алгоритме, описанном в вопросе , я весь внимание, но я не вижу, как это сделать, потому flock что блокировка типа работает с индексом, и каждый из трех процессов в гонке смотрит на другой индекс. Изменение, например, на файл блокировки, который всегда существует (поэтому все процессы применяются flock к одному и тому же индексу), невозможно из-за ограничений обратной совместимости.

Ответ №1:

Поскольку вы уточнили, что для совместимости индекс файла блокировки переделывается для каждого шкафчика, я бы предложил F_SETLK удалить файл блокировки, прежде чем отсоединять его.

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

 open(tmp, O_CLOEXEC|O_EXCL|...) -> fd
set_cloexec(fd)  // if no O_CLOEXEC
fcntl(fd, F_SETLCK, { .l_type = F_WRLCK, ...} )
write(fd, my_pid, ...)
link(tmp, lockfile) -> EEXIST

  // stale lockfile?
  lfd = open(lockfile, ...)
  // Give up if EACCES or EAGAIN
  fcntl(lfd, F_SETLCK, { .l_type = F_WRLCK, ...} ) -> 0
  unlink(lockfile)
  close(lfd)
 

В этом случае вы также можете пропустить логику убийства.

Комментарии:

1. Это звучит многообещающе. Мне придется провести кучу тестов, прежде чем принять ответ.