Você perguntou sobre o NFS. É provável que esse tipo de código seja quebrado no NFS, pois a verificação noclobber
envolve duas operações NFS separadas (verifique se existe um arquivo, crie um novo arquivo) e dois processos de dois clientes NFS separados podem entrar em uma condição de corrida em que ambos são bem-sucedidos ( ambos verificam se B.part
ainda não existe e, em seguida, continuam a criá-lo com êxito, como resultado da substituição um do outro.)
Não há realmente uma verificação genérica para saber se o sistema de arquivos para o qual você está escrevendo suportará algo como noclobber
atomicamente ou não. Você pode verificar o tipo de sistema de arquivos, seja NFS, mas isso seria uma heurística e não necessariamente uma garantia. Sistemas de arquivos como SMB / CIFS (Samba) provavelmente sofrem dos mesmos problemas. Os sistemas de arquivos expostos através do FUSE podem ou não se comportar corretamente, mas isso depende principalmente da implementação.
Uma abordagem possivelmente melhor é evitar a colisão na B.part
etapa, usando um nome de arquivo exclusivo (através da cooperação com outros agentes) para que você não precise depender denoclobber
. Por exemplo, você pode incluir, como parte do nome do arquivo, seu nome de host, PID e um carimbo de data / hora (+ possivelmente um número aleatório). Como deve haver um único processo em execução em um PID específico em um host a qualquer momento, isso deve garantir exclusividade.
Então, um dos seguintes:
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"
Portanto, se você tiver uma condição de corrida entre dois agentes, os dois continuarão com a operação, mas a última operação será atômica; portanto, B existe com uma cópia completa de A ou B não existe.
Você pode reduzir o tamanho da corrida verificando novamente após a cópia e antes da operação mv
ou ln
, mas ainda há uma pequena condição de corrida lá. Mas, independentemente da condição de corrida, o conteúdo de B deve ser consistente, supondo que os dois processos estejam tentando criá-lo a partir de A (ou uma cópia de um arquivo válido como origem).
Observe que na primeira situação em que mv
, quando existe uma corrida, o último processo é aquele que vence, pois renomear (2) substituirá atomicamente um arquivo existente:
Se o newpath já existir, ele será substituído atomicamente, para que não haja nenhum ponto em que outro processo que tente acessar o newpath o encontre ausente. [...]
Se o caminho novo existir, mas a operação falhar por algum motivo, rename()
garante que uma instância do caminho novo esteja em vigor.
Portanto, é bem possível que processos que consomem B ao mesmo tempo possam ver versões diferentes (inodes diferentes) durante esse processo. Se todos os escritores estiverem tentando copiar o mesmo conteúdo e os leitores estiverem consumindo o conteúdo do arquivo, isso pode ser bom, se eles tiverem inodes diferentes para arquivos com o mesmo conteúdo, eles ficarão felizes da mesma forma.
A segunda abordagem, usando um link físico , parece melhor, mas eu me lembro de fazer experimentos com links físicos em um loop restrito no NFS de muitos clientes simultâneos e contar com sucesso, e ainda parecia haver algumas condições de corrida, onde parecia que dois clientes emitiam um link físico operação ao mesmo tempo, com o mesmo destino, ambos pareciam ter sucesso. (É possível que esse comportamento tenha sido relacionado à implementação específica do servidor NFS, YMMV.) De qualquer forma, esse provavelmente é o mesmo tipo de condição de corrida, em que você pode acabar recebendo dois inodes separados para o mesmo arquivo nos casos em que há muito tráfego. concorrência entre escritores para acionar essas condições de corrida. Se seus escritores são consistentes (ambos copiam de A para B) e seus leitores estão consumindo apenas o conteúdo, isso pode ser suficiente.
Finalmente, você mencionou o bloqueio. Infelizmente, o bloqueio está em falta, pelo menos no NFSv3 (não tenho certeza sobre o NFSv4, mas aposto que também não é bom.) Se você está pensando em bloquear, deve procurar em diferentes protocolos para bloqueio distribuído, possivelmente fora de banda com o cópias de arquivos reais, mas isso é perturbador, complexo e propenso a problemas como conflitos, então eu diria que é melhor evitar.
Para obter mais informações sobre atomicidade no NFS, convém ler no formato de caixa de correio Maildir , criado para evitar bloqueios e trabalhar de maneira confiável, mesmo no NFS. Isso é feito mantendo nomes de arquivos exclusivos em todos os lugares (para que você nem obtenha um B final no final).
Talvez um pouco mais interessante para o seu caso em particular, o formato Maildir ++ estende o Maildir para adicionar suporte à cota da caixa de correio e o faz atualizando atomicamente um arquivo com um nome fixo dentro da caixa de correio (para que possa estar mais próximo do seu B.) Acho que o Maildir ++ tenta anexar, o que não é realmente seguro no NFS, mas existe uma abordagem de recálculo que usa um procedimento semelhante a esse e é válido como uma substituição atômica.
Espero que todos esses indicadores sejam úteis!