2012-07-23 10 views
8

Necesito copiar un archivo de una ubicación a otra, y necesito lanzar una excepción (o al menos de alguna manera reconocer) si el archivo ya existe en el destino (sin sobreescritura).Una operación de copia de archivo atómica segura

Puedo verificar primero con os.path.exists() pero es extremadamente importante que el archivo no se pueda crear en el lapso de tiempo entre la verificación y la copia.

¿Hay una manera incorporada de hacer esto, o hay una manera de definir una acción como atómica?

+0

¿Es solo la creación del destino que debe ser atómica, pero también tiene el contenido fuente, como leído, representa solo un punto en el tiempo? –

+0

Solo la creación. Estoy escribiendo un programa que copia un archivo de zona en/tmp, realiza los cambios necesarios y luego lo copia al final. Solo necesito asegurarme de que si dos personas intentan editar al mismo tiempo, uno de ellos no pierde sus cambios. – Rory

+2

Tenga en cuenta que 'rename()' solo es atómico si el origen y el destino están en el mismo sistema de archivos, por lo que es posible que desee crear su archivo temporal en el directorio de destino, no en '/ tmp'. –

Respuesta

7

Hay es en realidad una manera de hacer esto, de forma atómica y segura, proporcionan todos los actores lo hacen de la misma manera. Es una adaptación de la lock-free whack-a-mole algorithm, y no del todo trivial, así que siéntase libre para ir con un "no" como respuesta general;)

Qué hacer

  1. Compruebe si el archivo ya existe. Detener si lo hace
  2. Generate a unique ID
  3. Copie el archivo de origen en la carpeta de destino con un nombre temporal, por ejemplo, <target>.<UUID>.tmp.
  4. Renombrar la copia <target>-<UUID>.mole.tmp.
  5. Look for any other files matching the pattern<target>-*.mole.tmp.
    • Si su UUID es superior al suyo, attempt to delete it. (No se preocupe si ya no está).
    • Si su UUID es inferior al suyo, intente eliminar el suyo. (De nuevo, no se preocupe si ya no está). De ahora en adelante, trate su UUID como si fuera suyo.
  6. Vuelva a verificar si el archivo de destino ya existe. Si es así, intente eliminar su archivo temporal. (No se preocupe si se ha ido. Recuerde que su UUID puede haber cambiado en el paso 5.)
  7. Si no intentó borrarlo en el paso 6, intente cambiar el nombre de su archivo temporal a su nombre final, <target>. (No se preocupe si se ha ido, simplemente vuelva al paso 5.)

¡Ya ha terminado!

Cómo funciona

Imagínese cada archivo fuente candidato es un topo que sale de su agujero. A medio camino, hace una pausa y golpea a los topos que compiten contra el suelo, antes de comprobar que ningún otro topo ha emergido por completo. Si ejecuta esto en su cabeza, debería ver que solo un lunar lo logrará hasta el final. Para evitar este sistema desde livelocking, agregamos un orden total en el cual el topo puede golpear cuál. Bam! A   Tesis doctoral   lock-free algorithm.

El paso 4 puede parecer innecesario — ¿por qué no utilizar ese nombre en primer lugar? Sin embargo, otro proceso puede "adoptar" su archivo   mole   en el paso 5, y hacerlo ganador en el paso 7, por lo que es muy importante que aún no esté escribiendo los contenidos. Los cambios de nombre en el mismo sistema de archivos son atómicos, por lo que el paso 4 es seguro.

+1

Ese es un hermoso algoritmo. No creo que lo haga IRL, pero el enfoque es ingenioso. – Rory

+0

¿esto se basa en que el uuid produce un valor incremental? – coolfeature

+0

@coolfeature No, el orden es solo para asegurar que un ganador finalmente se seleccione. –

11

No hay forma de hacerlo; las operaciones de copia de archivos nunca son atómicas y no hay forma de hacerlas.

Pero puede escribir el archivo con un nombre temporal aleatorio y luego renombrarlo. Las operaciones de cambio de nombre deben ser atómicas. Si el archivo ya existe, el cambio de nombre fallará y obtendrá un error.

[Edit2]rename() sólo es atómica si lo hace en el mismo sistema de archivos. La forma más segura es crear el nuevo archivo en la misma carpeta que el destino.

[EDIT] Se discute mucho si el cambio de nombre es siempre atómico o no y sobre el comportamiento de sobrescritura. Así que desenterré algunos recursos.

En Linux, si el destino existe y tanto el origen como el destino son archivos, el destino se sobrescribe silenciosamente (man page). Así que estaba equivocado allí.

Pero rename(2) aún garantiza que el archivo original o el nuevo archivo siguen siendo válidos si algo sale mal, por lo que la operación es atómica en el sentido de que no puede dañar los datos. No es atómico en el sentido de que impide que dos procesos hagan el mismo cambio de nombre al mismo tiempo y usted puede predecir el resultado. Uno ganará pero no puede decir cuál.

En Windows, si otro proceso está actualmente escribiendo el archivo, se obtiene un error si intenta abrirlo para escritura, por lo que una ventaja para Windows, aquí.

Si su computadora se cuelga mientras la operación se escribe en el disco, la implementación del sistema de archivos decidirá la cantidad de datos que se corrompen. No es nada una aplicación podría hacer al respecto. Así que deja de lloriquear ya :-)

Tampoco hay otro enfoque que funcione mejor o incluso tan bien como este.

Puede usar el bloqueo de archivos en su lugar. Pero eso simplemente haría que todo fuera más complejo y no arrojaría ventajas adicionales (además de ser más complicado que algunas personas ven como una gran ventaja por alguna razón). Y agregaría muchos buenos casos de esquina cuando su archivo esté en una unidad de red.

Puede usar open(2) con el modo O_CREAT que haría que la función falle si el archivo ya existe. Pero eso no evitaría que un segundo proceso elimine el archivo y escriba su propia copia.

O podría crear un directorio de bloqueo ya que la creación de directorios tiene que ser atómica también. Pero eso tampoco te compraría mucho. Tendría que escribir el código de bloqueo usted mismo y hacer absolutamente, 100% seguro de que realmente, realmente siempre elimina el directorio de bloqueo en caso de desastre, que no puede.

+0

Solo si se vacía y se sincroniza antes de cambiar el nombre. – dcolish

+0

Por curiosidad, ¿cómo sería la parte de cambio de nombre de este trabajo? 'os.rename' no funcionará en Unix. – mgilson

+0

@dcolish No es necesario sincronizar: el enjuague o 'close()' ing es suficiente. –

Cuestiones relacionadas