Ви запитали про 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
, коли гонка існує, останній процес - це той, хто виграє, оскільки перейменування (2) атомно замінить існуючий файл:
Якщо newpath вже існує, він буде атомно замінений, так що немає жодного моменту, коли інший процес, який намагається отримати доступ до newpath, визнає його відсутнім. [...]
Якщо newpath існує, але операція з якоїсь причини не вдається, rename()
гарантує залишити екземпляр newpath на місці.
Отже, цілком можливі процеси, що споживають B у той час, можуть бачити різні його версії (різні вставки) під час цього процесу. Якщо автори просто намагаються скопіювати один і той же вміст, а читачі просто споживають вміст файлу, це може бути добре, якщо вони отримають різні вставки для файлів з однаковим вмістом, вони будуть щасливі однаково.
Другий підхід із використанням жорсткого посилання виглядає краще, але я пам’ятаю, що робив експерименти із жорсткими посиланнями в тісному циклі на NFS від багатьох одночасних клієнтів і рахував успіх, і все ще, здавалося, існували деякі умови гонки там, де, здавалося, два клієнти видали жорстке посилання операція одночасно, з одним і тим же призначенням, обидва здавалося успішними. (Можливо, що така поведінка була пов’язана з конкретною реалізацією сервера NFS, YMMV.) У будь-якому випадку, це, мабуть, той самий вид перегонів, де ви, в кінцевому підсумку, отримаєте два окремих введення для одного файлу у випадках, коли важкі паралельність між авторами, щоб викликати ці перегони. Якщо ваші автори послідовні (обидва копіюють від А до Б), а ваші читачі споживають лише вміст, цього може бути достатньо.
Нарешті, ви згадали про блокування. На жаль, блокування сильно не вистачає, принаймні, у NFSv3 (не впевнений у NFSv4, але я б обміняв, що це теж не добре.) Якщо ви розглядаєте можливість блокування, вам слід розглянути різні протоколи розподіленого блокування, можливо, поза діапазоном із фактичні копії файлів, але це є як руйнівним, складним, так і схильним до таких питань, як тупики, тому я б сказав, що цього краще уникати.
Для отримання додаткової інформації щодо атомності на NFS, ви можете прочитати у форматі поштової скриньки Maildir , який був створений для уникнення блокування та надійної роботи навіть на NFS. Це робиться, зберігаючи унікальні імена файлів скрізь (так що ви навіть не отримаєте остаточний B в кінці).
Можливо, дещо цікавіше вашому конкретному випадку, формат Maildir ++ розширює Maildir, щоб додати підтримку квоти поштової скриньки, і робить це шляхом атомного оновлення файлу з фіксованим іменем всередині поштової скриньки (щоб це було ближче до вашого Б.). Я думаю, що Maildir ++ намагається додавати, що насправді не є безпечним для NFS, але існує підхід для перерахунку, який використовує процедуру, аналогічну цій, і діє як атомна заміна.
Сподіваємось, всі ці покажчики будуть корисні!