Vous avez posé des questions sur NFS. Ce type de code risque de casser sous NFS, car la vérification denoclobber
implique deux opérations NFS distinctes (vérifier si le fichier existe, créer un nouveau fichier) et deux processus de deux clients NFS distincts peuvent entrer dans une condition de concurrence critique où les deux réussissent ( les deux vérifient qu'il B.part
n'existe pas encore, puis les deux procèdent à sa création, ils se remplacent donc.)
Il n'y a pas vraiment de vérification générique pour savoir si le système de fichiers sur lequel vous écrivez prendra en charge quelque chose comme noclobber
atomiquement ou non. Vous pouvez vérifier le type de système de fichiers, que ce soit NFS, mais ce serait une heuristique et pas nécessairement une garantie. Les systèmes de fichiers comme SMB / CIFS (Samba) sont susceptibles de souffrir des mêmes problèmes. Les systèmes de fichiers exposés via FUSE peuvent ou non se comporter correctement, mais cela dépend principalement de l'implémentation.
Une approche peut-être meilleure consiste à éviter la collision à l' B.part
étape, en utilisant un nom de fichier unique (grâce à la coopération avec d'autres agents) afin que vous n'ayez pas à dépendre denoclobber
. Par exemple, vous pouvez inclure, dans le cadre du nom de fichier, votre nom d'hôte, votre PID et un horodatage (+ éventuellement un nombre aléatoire). garantir l'unicité.
Donc, soit l'un des:
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.
Ou:
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"
Donc, si vous avez une condition de concurrence entre deux agents, ils procéderont tous les deux à l'opération, mais la dernière opération sera atomique, donc soit B existe avec une copie complète de A, soit B n'existe pas.
Vous pouvez réduire la taille de la course en vérifiant à nouveau après la copie et avant le mv
ouln
opération , mais il y a encore une petite condition de course. Mais, quelle que soit la condition de concurrence, le contenu de B doit être cohérent, en supposant que les deux processus tentent de le créer à partir de A (ou d'une copie d'un fichier valide comme origine.)
Notez que dans la première situation avec mv
, lorsqu'une course existe, le dernier processus est celui qui gagne, car rename (2) remplacera atomiquement un fichier existant:
Si newpath existe déjà, il sera atomiquement remplacé, de sorte qu'il n'y ait aucun point auquel un autre processus tentant d'accéder à newpath le trouvera manquant. [...]
Si newpath existe mais que l'opération échoue pour une raison quelconque, rename()
garantit de laisser une instance de newpath en place.
Il est donc tout à fait possible que les processus consommant B à l'époque puissent en voir différentes versions (différents inodes) au cours de ce processus. Si les rédacteurs essaient tous de copier le même contenu et que les lecteurs consomment simplement le contenu du fichier, cela pourrait être bien, s'ils obtiennent des inodes différents pour des fichiers avec le même contenu, ils seront tout de même satisfaits.
La deuxième approche utilisant un lien dur semble meilleure, mais je me souviens avoir fait des expériences avec des liens durs dans une boucle étroite sur NFS à partir de nombreux clients simultanés et compter le succès et il semblait toujours y avoir des conditions de concurrence là-bas, où il semblait que deux clients avaient émis un lien dur opération en même temps, avec la même destination, les deux semblaient réussir. (Il est possible que ce comportement soit lié à l'implémentation particulière du serveur NFS, YMMV.) Dans tous les cas, il s'agit probablement du même type de condition de concurrence, où vous pourriez finir par obtenir deux inodes distincts pour le même fichier dans les cas où il y a du lourd concurrence entre les écrivains pour déclencher ces conditions de concurrence. Si vos rédacteurs sont cohérents (copiant tous les deux de A à B) et que vos lecteurs n'en consomment que le contenu, cela pourrait suffire.
Enfin, vous avez mentionné le verrouillage. Malheureusement, le verrouillage fait gravement défaut, au moins dans NFSv3 (je ne suis pas sûr de NFSv4, mais je parierais que ce n'est pas bon non plus.) Si vous envisagez de verrouiller, vous devriez examiner différents protocoles pour le verrouillage distribué, éventuellement hors bande avec le les copies de fichiers réelles, mais qui sont à la fois perturbatrices, complexes et sujettes à des problèmes tels que les blocages, donc je dirais qu'il vaut mieux être évité.
Pour plus d'informations sur le sujet de l'atomicité sur NFS, vous voudrez peut-être lire sur le format de boîte aux lettres Maildir , qui a été créé pour éviter les verrous et fonctionner de manière fiable même sur NFS. Il le fait en gardant des noms de fichiers uniques partout (de sorte que vous n'obtenez même pas un B final à la fin.)
Peut-être un peu plus intéressant dans votre cas particulier, le format Maildir ++ étend Maildir pour ajouter la prise en charge du quota de boîte aux lettres et le fait en mettant à jour atomiquement un fichier avec un nom fixe à l'intérieur de la boîte aux lettres (de sorte que cela pourrait être plus proche de votre B.) Je pense que Maildir ++ essaie à ajouter, ce qui n'est pas vraiment sûr sur NFS, mais il existe une approche de recalcul qui utilise une procédure similaire à celle-ci et elle est valide en tant que remplacement atomique.
Espérons que tous ces pointeurs vous seront utiles!