Вы спрашивали о NFS. Этот вид кода, скорее всего, сломается в NFS, поскольку проверка noclobber
включает две отдельные операции NFS (проверка, существует ли файл, создание нового файла) и два процесса из двух отдельных клиентов NFS могут попасть в состояние состязания, когда оба они преуспеют ( оба проверяют, что B.part
еще не существует, затем оба продолжают успешно создавать его, в результате они перезаписывают друг друга.)
На самом деле не нужно делать общую проверку того, будет ли файловая система, в которую вы пишете, поддерживать что-то наподобие noclobber
атомарной или нет. Вы можете проверить тип файловой системы, будь то NFS, но это будет эвристическим и не обязательно гарантией. Файловые системы, такие как SMB / CIFS (Samba), могут страдать от тех же проблем. Файловые системы, предоставляемые через FUSE, могут или не могут вести себя правильно, но это в основном зависит от реализации.
Возможно, лучший подход состоит в том, чтобы избежать столкновения на этом B.part
этапе, используя уникальное имя файла (благодаря сотрудничеству с другими агентами), чтобы вам не нужно было зависеть noclobber
. Например, вы можете включить, как часть имени файла, ваше имя хоста, PID и временную метку (+ возможно, случайное число). Поскольку должен быть один процесс, выполняющийся под определенным PID на хосте в любой момент времени, это должно гарантия уникальности.
Так что либо одно из:
test -f B && continue # skip already existing
unique=$(hostname).$$.$(date +%s).$RANDOM
cp A B.part."$unique"
# Maybe check for existance of B again, remove
# the temporary file and bail out in that case.
mv B.part."$unique" B
# mv (rename) should always succeed, overwrite a
# previously copied B if one exists.
Или:
test -f B && continue # skip already existing
unique=$(hostname).$$.$(date +%s).$RANDOM
cp A B.part."$unique"
if ln B.part."$unique" B ; then
echo "Success creating B"
else
echo "Failed creating B, already existed"
fi
# Both cases require cleanup.
rm B.part."$unique"
Таким образом, если у вас есть условие состязания между двумя агентами, они оба продолжат выполнение операции, но последняя операция будет атомарной, поэтому либо B существует с полной копией A, либо B не существует.
Вы можете уменьшить размер гонки, проверив еще раз после копирования и перед операцией mv
или ln
, но там все еще есть небольшое состояние гонки. Но, независимо от состояния гонки, содержимое B должно быть согласованным, если предположить, что оба процесса пытаются создать его из A (или копии из действительного файла в качестве источника).
Обратите внимание, что в первой ситуации mv
, когда существует гонка, выигрывает последний процесс, поскольку rename (2) атомно заменит существующий файл:
Если newpath уже существует, он будет атомарно заменен, так что не будет точки, в которой другой процесс, пытающийся получить доступ к newpath , обнаружит, что он отсутствует. [...]
Если newpath существует, но операция по какой-то причине завершается неудачно, rename()
гарантирует сохранение экземпляра newpath на месте.
Таким образом, вполне возможно, что процессы, потребляющие B в то время, могут видеть разные его версии (разные inode) во время этого процесса. Если авторы всего лишь пытаются скопировать одно и то же содержимое, а читатели просто используют содержимое файла, это может быть хорошо, если они получат разные inode для файлов с одинаковым содержимым, они будут счастливы одинаково.
Второй подход с использованием жесткой ссылки выглядит лучше, но я вспоминаю, как проводил эксперименты с жесткими ссылками в узком цикле на NFS от многих одновременных клиентов и подсчитывал успех, и все же, похоже, были некоторые условия гонки, когда казалось, что два клиента выпустили жесткую ссылку операции в одно и то же время с одним и тем же пунктом назначения, похоже, были успешными. (Возможно, это поведение было связано с конкретной реализацией NFS-сервера, YMMV.) В любом случае, это, вероятно, тот же тип состояния гонки, когда вы можете получить два отдельных inode для одного и того же файла в случаях, когда есть большие параллелизм между авторами, чтобы вызвать эти условия гонки. Если ваши авторы последовательны (оба копируют от A до B), и ваши читатели потребляют только содержимое, этого может быть достаточно.
Наконец, вы упомянули блокировку. К сожалению, строго отсутствует блокировка, по крайней мере, в NFSv3 (не уверен насчет NFSv4, но я бы поспорил, что это тоже не хорошо.) Если вы рассматриваете блокировку, вы должны изучить различные протоколы для распределенной блокировки, возможно, вне полосы с фактические копии файлов, но это разрушительно, сложно и подвержено таким проблемам, как взаимоблокировки, поэтому я бы сказал, что лучше избегать.
Для получения дополнительной информации об атомарности в NFS вы можете прочитать в формате почтового ящика Maildir , который был создан, чтобы избежать блокировок и надежно работать даже в NFS. Он делает это, сохраняя уникальные имена файлов везде (так что вы даже не получите окончательный B в конце).
Возможно, несколько более интересный для вашего конкретного случая, формат Maildir ++ расширяет Maildir для добавления поддержки квоты почтового ящика и делает это путем атомарного обновления файла с фиксированным именем внутри почтового ящика (так, чтобы он был ближе к вашему B.) Я думаю, что Maildir ++ пытается добавить, что на самом деле небезопасно для NFS, но есть метод пересчета, который использует процедуру, подобную этой, и он действителен как атомарная замена.
Надеюсь, все эти указатели будут полезны!