Preguntaste sobre NFS. Es probable que este tipo de código se rompa bajo NFS, ya que la verificación noclobber
implica dos operaciones NFS separadas (verificar si existe un archivo, crear un nuevo archivo) y dos procesos de dos clientes NFS separados pueden entrar en una condición de carrera donde ambos tienen éxito ( ambos verifican que B.part
todavía no existe, luego ambos proceden a crearlo con éxito, como resultado se sobrescriben entre sí).
Realmente no hay que hacer una verificación genérica para saber si el sistema de archivos en el que está escribiendo admitirá algo como noclobber
atómicamente o no. Puede verificar el tipo de sistema de archivos, ya sea NFS, pero eso sería heurístico y no necesariamente una garantía. Es probable que los sistemas de archivos como SMB / CIFS (Samba) sufran los mismos problemas. Los sistemas de archivos expuestos a través de FUSE pueden o no comportarse correctamente, pero eso depende principalmente de la implementación.
Un enfoque posiblemente mejor es evitar la colisión en el B.part
paso, utilizando un nombre de archivo único (a través de la cooperación con otros agentes) para que no necesite depender de él noclobber
. Por ejemplo, podría incluir, como parte del nombre de archivo, su nombre de host, PID y una marca de tiempo (+ posiblemente un número aleatorio). Dado que debería haber un solo proceso ejecutándose bajo un PID específico en un host en un momento dado, esto debería Garantizar la unicidad.
Entonces cualquiera de:
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.
O:
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"
Entonces, si tiene una condición de carrera entre dos agentes, ambos procederán con la operación, pero la última operación será atómica, por lo que B existe con una copia completa de A o B no existe.
Puede reducir el tamaño de la carrera por el control de nuevo después de la copia y antes de que el mv
o la ln
operación, pero todavía hay una condición de carrera pequeña allí. Pero, independientemente de la condición de carrera, el contenido de B debe ser coherente, suponiendo que ambos procesos estén tratando de crearlo desde A (o una copia de un archivo válido como origen).
Tenga en cuenta que en la primera situación con mv
, cuando existe una carrera, el último proceso es el que gana, ya que renombrar (2) reemplazará atómicamente un archivo existente:
Si newpath ya existe, será reemplazado atómicamente, de modo que no haya ningún punto en el que otro proceso que intente acceder a newpath lo encuentre perdido. [...]
Si newpath existe pero la operación falla por alguna razón, rename()
garantiza dejar una instancia de newpath en su lugar.
Por lo tanto, es muy posible que los procesos que consumen B en ese momento puedan ver diferentes versiones de él (diferentes inodos) durante este proceso. Si los escritores simplemente intentan copiar el mismo contenido, y los lectores simplemente consumen el contenido del archivo, eso podría estar bien, si obtienen diferentes inodos para archivos con el mismo contenido, estarán contentos de todos modos.
El segundo enfoque que usa un enlace duro se ve mejor, pero recuerdo haber hecho experimentos con enlaces duros en un circuito cerrado en NFS de muchos clientes concurrentes y contando el éxito y todavía parecía haber algunas condiciones de carrera allí, donde parecía que dos clientes emitían un enlace duro operación al mismo tiempo, con el mismo destino, ambos parecían tener éxito. (Es posible que este comportamiento esté relacionado con la implementación particular del servidor NFS, YMMV). En cualquier caso, es probable que sea el mismo tipo de condición de carrera, donde podría terminar obteniendo dos inodos separados para el mismo archivo en los casos en que haya mucha carga. concurrencia entre escritores para desencadenar estas condiciones de carrera. Si sus escritores son consistentes (ambos copian A a B), y sus lectores solo consumen el contenido, eso podría ser suficiente.
Finalmente, mencionaste el bloqueo. Desafortunadamente, el bloqueo es muy deficiente, al menos en NFSv3 (no estoy seguro acerca de NFSv4, pero apuesto a que tampoco es bueno). Si está considerando bloquear, debería buscar diferentes protocolos para el bloqueo distribuido, posiblemente fuera de banda con el copias de archivos reales, pero eso es a la vez perjudicial, complejo y propenso a problemas como puntos muertos, por lo que diría que es mejor evitarlo.
Para obtener más información sobre el tema de la atomicidad en NFS, es posible que desee leer en el formato de buzón Maildir , que fue creado para evitar bloqueos y funcionar de manera confiable incluso en NFS. Lo hace manteniendo nombres de archivo únicos en todas partes (para que ni siquiera obtenga una B final al final).
Quizás algo más interesante para su caso particular, el formato Maildir ++ extiende Maildir para agregar soporte para la cuota del buzón y lo hace actualizando atómicamente un archivo con un nombre fijo dentro del buzón (para que pueda estar más cerca de su B). Creo que Maildir ++ intenta para agregar, que no es realmente seguro en NFS, pero hay un enfoque de recálculo que utiliza un procedimiento similar a este y es válido como un reemplazo atómico.
¡Esperemos que todos estos consejos sean útiles!