2012-07-23 15 views
8

Ho bisogno di copiare un file da una posizione a un'altra, e ho bisogno di lanciare un'eccezione (o almeno in qualche modo riconoscere) se il file esiste già nella destinazione (nessuna sovrascrittura).Un'operazione di copia di file atomica sicura

Posso controllare prima con os.path.exists() ma è estremamente importante che il file non possa essere creato nel breve lasso di tempo tra il controllo e la copia.

Esiste un modo integrato per farlo oppure esiste un modo per definire un'azione come atomica?

+0

È solo la creazione della destinazione che deve essere atomica, ma anche il contenuto sorgente, come letto, rappresenta solo un singolo punto nel tempo? –

+0

Proprio la creazione. Sto scrivendo un programma che copia un file di zona in/tmp, apporta le modifiche richieste e poi lo copia alla fine. Devo solo assicurarmi che se due persone provano e modificano contemporaneamente, una di queste non perde le modifiche. – Rory

+2

Ricorda che 'rename()' è atomico solo se l'origine e la destinazione si trovano sullo stesso file system, quindi potresti voler creare il tuo file temporaneo nella directory di destinazione, non in '/ tmp'. –

risposta

7

Ci è in realtà un modo per fare questo, atomicamente e sicuro, purché tutti gli attori lo fanno nello stesso modo. Si tratta di un adattamento del lock-free whack-a-mole algorithm, e non del tutto banale, quindi sentitevi liberi di andare con "no" come risposta generale;)

Cosa fare

  1. Verificare se il file esiste già. Fermati se lo fa.
  2. Generate a unique ID
  3. Copia il file di origine nella cartella di destinazione con un nome temporaneo, ad esempio <target>.<UUID>.tmp.
  4. Rename la copia <target>-<UUID>.mole.tmp.
  5. Look for any other files matching the pattern<target>-*.mole.tmp.
    • Se il loro UUID è maggiore del proprio, attempt to delete it. (Non preoccuparti se non funziona.)
    • Se il loro UUID confronta meno del tuo, prova a eliminare il tuo. (Di nuovo, non ti preoccupare se è andato.) Da ora in poi, considera il loro UUID come se fosse il tuo.
  6. Controllare di nuovo se il file di destinazione esiste già. In tal caso, provare a eliminare il file temporaneo. (Non preoccuparti se è andato. Ricorda che il tuo UUID potrebbe essere cambiato nel passaggio 5.)
  7. Se non hai già provato a eliminarlo nel passaggio 6, prova a rinominare il file temporaneo con il nome finale, <target>. (Non preoccuparti se non c'è più, torna semplicemente al punto 5.)

Il gioco è fatto!

Come funziona

Immaginate ogni file sorgente candidato è una talpa che esce dalla sua tana. A metà strada, si ferma e rimette a terra le talpe in competizione, prima di controllare che non sia emersa completamente un'altra talpa. Se lo fai passare per la testa, dovresti vedere che solo una talpa uscirà completamente. Per impedire questo sistema da livelocking, aggiungiamo un ordinamento totale su quale mole può colpire quale. Bam! A   Tesi di dottorato   lock-free algorithm.

Il passaggio 4 potrebbe sembrare non necessario — perché non utilizzare solo quel nome in primo luogo? Tuttavia, un altro processo può "adottare" il tuo file talpa nel passaggio 5 e renderlo il vincitore nel passaggio 7, quindi è molto importante che tu non stia ancora scrivendo il contenuto! I nomi rinominati sullo stesso file system sono atomici, quindi il passaggio 4 è sicuro.

+1

Questo è un bellissimo algoritmo. Non penso di farlo IRL ma l'approccio è geniale. – Rory

+0

si basa sull'uuu che produce un valore incrementale? – coolfeature

+0

@coolfeature No, l'ordine serve solo a garantire che venga selezionato un vincitore. –

11

Non c'è modo di farlo; le operazioni di copia dei file non sono mai atomiche e non c'è modo di farle.

Ma è possibile scrivere il file con un nome temporaneo casuale e quindi rinominare. Rinominare le operazioni devono essere atomiche. Se il file esiste già, la ridenominazione fallirà e riceverai un errore.

[EDIT2]rename() è atomico solo se lo si fa nello stesso file system. Il modo sicuro è creare il nuovo file nella stessa cartella della destinazione.

[EDIT] Si discute molto se la rinomina è sempre atomica o meno e sul comportamento di sovrascrittura. Quindi ho scavato alcune risorse.

Su Linux, se la destinazione esiste e sia l'origine che la destinazione sono file, la destinazione viene automaticamente sovrascritta (man page). Quindi mi sono sbagliato lì.

Ma rename(2) garantisce comunque che il file originale o il nuovo file rimangano validi se qualcosa va storto, quindi l'operazione è atomica nel senso che non può danneggiare i dati. Non è atomico, nel senso che impedisce a due processi di fare lo stesso rinominare contemporaneamente e si può predire il risultato. Uno vincerà ma non puoi dire quale.

Su Windows, se un altro processo sta attualmente scrivendo il file, si ottiene un errore se si tenta di aprirlo per la scrittura, quindi un vantaggio per Windows, qui.

Se il computer si arresta in modo anomalo mentre l'operazione viene scritta su disco, l'implementazione del file system deciderà la quantità di dati danneggiati. C'è niente un'applicazione potrebbe fare al riguardo. Quindi smettila di piagnucolare :-)

Non c'è anche nessun altro approccio che funzioni meglio o anche solo come questo.

È possibile utilizzare il blocco dei file. Ma ciò renderebbe tutto più complesso e non offrirà ulteriori vantaggi (oltre ad essere più complicato che alcune persone considerano un enorme vantaggio per qualche motivo). E aggiungeresti molti bei casi d'angolo quando il tuo file si trova su un'unità di rete.

È possibile utilizzare open(2) con la modalità O_CREAT che renderebbe la funzione non riuscita se il file esiste già. Ma ciò non impedirebbe a un secondo processo di eliminare il file e scrivere la propria copia.

Oppure è possibile creare una directory di blocco poiché la creazione di directory deve essere anche atomica. Ma questo non ti comprerebbe molto. Dovresti scrivere tu stesso il codice di blocco e fare assolutamente, sicuro al 100% che tu davvero, in realtà cancelli sempre la directory di blocco in caso di disastro - cosa che non puoi.

+0

Solo se si esegue il flush e la sincronizzazione prima di rinominare. – dcolish

+0

Per curiosità, come sarebbe la porzione di rinomina di questo lavoro? 'os.rename' non funzionerà su Unix. – mgilson

+0

@dcolish Non è necessario sincronizzare: lo svuotamento o il comando 'close()' è sufficiente. –

Problemi correlati