Hai chiesto di NFS. È probabile che questo tipo di codice si interrompa in NFS, poiché il controllo per noclobber
coinvolge due operazioni NFS separate (verifica se il file esiste, crea un nuovo file) e due processi da due client NFS separati possono entrare in una condizione di competizione in cui entrambi hanno esito positivo ( entrambi verificano che B.part
non esista ancora, quindi entrambi procedono a crearlo correttamente, di conseguenza si sovrascrivono a vicenda.)
Non c'è davvero da fare un controllo generico per verificare se il filesystem su cui stai scrivendo supporterà qualcosa di simile noclobber
atomicamente o no. È possibile verificare il tipo di filesystem, sia esso NFS, ma sarebbe euristico e non necessariamente una garanzia. I filesystem come SMB / CIFS (Samba) potrebbero soffrire degli stessi problemi. I filesystem esposti attraverso FUSE possono o meno comportarsi correttamente, ma ciò dipende principalmente dall'implementazione.
Un approccio forse migliore è quello di evitare la collisione nel B.part
passaggio, utilizzando un nome file univoco (attraverso la cooperazione con altri agenti) in modo da non dover dipendere noclobber
. Ad esempio, potresti includere, come parte del nome file, il tuo nome host, PID e un timestamp (+ possibilmente un numero casuale.) Dal momento che dovrebbe esserci un singolo processo in esecuzione sotto un PID specifico in un host in qualsiasi momento, questo dovrebbe garantire unicità.
Quindi uno dei due:
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"
Quindi se hai una condizione di competizione tra due agenti, entrambi procederanno con l'operazione, ma l'ultima operazione sarà atomica, quindi B esiste con una copia completa di A o B non esiste.
Puoi ridurre le dimensioni della gara controllando di nuovo dopo la copia e prima dell'operazione mv
o ln
, ma c'è ancora una piccola condizione di gara. Ma, indipendentemente dalle condizioni della razza, il contenuto di B dovrebbe essere coerente, supponendo che entrambi i processi stiano cercando di crearlo da A (o una copia da un file valido come origine).
Si noti che nella prima situazione con mv
, quando esiste una gara, l'ultimo processo è quello che vince, poiché rename (2) sostituirà atomicamente un file esistente:
Se newpath esiste già, verrà sostituito atomicamente, in modo che non vi sia alcun punto in cui un altro processo che tenta di accedere a newpath lo trovi mancante. [...]
Se newpath esiste ma l'operazione non riesce per qualche motivo, rename()
garantisce di lasciare un'istanza di newpath in atto.
Pertanto, è possibile che i processi che consumano B al momento possano vedere versioni diverse di esso (inode diversi) durante questo processo. Se gli autori stanno solo provando a copiare lo stesso contenuto e i lettori stanno semplicemente consumando il contenuto del file, ciò potrebbe andare bene, se ottengono inode diversi per file con lo stesso contenuto, saranno felici lo stesso.
Il secondo approccio usando un hard link sembra migliore, ma ricordo di aver fatto esperimenti con hardlink in un circuito stretto su NFS da molti client concorrenti e contare il successo e sembra che ci siano ancora delle condizioni di gara lì, dove sembra che se due client emettano un hardlink operazione allo stesso tempo, con la stessa destinazione, entrambi sembravano avere successo. (È possibile che questo comportamento fosse correlato alla particolare implementazione del server NFS, YMMV.) In ogni caso, è probabilmente lo stesso tipo di condizione di competizione, in cui potresti finire per ottenere due inode separati per lo stesso file nei casi in cui è pesante concorrenza tra scrittori per innescare queste condizioni di gara. Se i tuoi autori sono coerenti (entrambi copiano da A a B) e i tuoi lettori ne consumano solo i contenuti, ciò potrebbe essere sufficiente.
Infine, hai menzionato il blocco. Sfortunatamente il blocco è gravemente carente, almeno in NFSv3 (non sono sicuro di NFSv4, ma scommetto che non va bene neanche.) Se stai considerando il blocco, dovresti esaminare diversi protocolli per il blocco distribuito, possibilmente fuori banda con il copie effettive dei file, ma questo è sia dirompente, complesso e soggetto a problemi come deadlock, quindi direi che è meglio evitare.
Per ulteriori informazioni sul tema dell'atomicità su NFS, potresti voler leggere sul formato della cassetta postale di Maildir , che è stato creato per evitare blocchi e funzionare in modo affidabile anche su NFS. Lo fa mantenendo nomi di file univoci ovunque (quindi alla fine non ottieni nemmeno una B finale).
Forse un po 'più interessante per il tuo caso particolare, il formato Maildir ++ estende Maildir per aggiungere il supporto per la quota della cassetta postale e lo fa aggiornando atomicamente un file con un nome fisso all'interno della cassetta postale (quindi potrebbe essere più vicino al tuo B.) Penso che Maildir ++ ci provi aggiungere, che non è davvero sicuro su NFS, ma esiste un approccio di ricalcolo che utilizza una procedura simile a questa ed è valido come sostituto atomico.
Spero che tutti questi suggerimenti siano utili!