Em 19 de agosto de 2013, Randal L. Schwartz publicou este shell script, que pretendia garantir, no Linux, "que apenas uma instância do [the] script esteja sendo executada, sem condições de corrida ou com a necessidade de limpar arquivos de bloqueio":
#!/bin/sh
# randal_l_schwartz_001.sh
(
if ! flock -n -x 0
then
echo "$$ cannot get flock"
exit 0
fi
echo "$$ start"
sleep 10 # for testing. put the real task here
echo "$$ end"
) < $0
Parece funcionar como anunciado:
$ ./randal_l_schwartz_001.sh & ./randal_l_schwartz_001.sh
[1] 11863
11863 start
11864 cannot get flock
$ 11863 end
[1]+ Done ./randal_l_schwartz_001.sh
$
Aqui está o que eu entendo:
- O script redireciona (
<
) uma cópia de seu próprio conteúdo (isto é, de$0
) para o STDIN (isto é, descritor de arquivo0
) de um subshell. - Dentro do subshell, o script tenta obter um lock exclusivo (não bloqueador
flock -n -x
) no descritor de arquivo0
.- Se essa tentativa falhar, o subshell será encerrado (e o script principal também, pois não há mais nada a fazer).
- Se a tentativa for bem-sucedida, o subshell executará a tarefa desejada.
Aqui estão as minhas perguntas:
- Por que o script precisa redirecionar, para um descritor de arquivo herdado pelo subshell, uma cópia de seu próprio conteúdo, em vez de, digamos, o conteúdo de outro arquivo? (Tentei redirecionar de um arquivo diferente e executar novamente como acima, e a ordem de execução mudou: a tarefa sem segundo plano ganhou o bloqueio antes da anterior. Portanto, talvez o uso do próprio conteúdo do arquivo evite condições de corrida; mas como?)
- Por que o script precisa redirecionar, para um descritor de arquivo herdado pelo subshell, uma cópia do conteúdo de um arquivo, afinal?
- Por que manter um bloqueio exclusivo no descritor de arquivo
0
em um shell impede que uma cópia do mesmo script, executado em um shell diferente, obtenha um bloqueio exclusivo no descritor de arquivo0
? Não conchas têm suas próprias cópias, separadas dos descritores de arquivo padrão (0
,1
e2
, ou seja, STDIN, STDOUT e STDERR)?
linux
shell-script
io-redirection
subshell
lock
sampablokuper
fonte
fonte
Respostas:
Você pode usar qualquer arquivo, desde que todas as cópias do script usem o mesmo. O uso
$0
apenas vincula o bloqueio ao próprio script: se você copiar o script e modificá-lo para outro uso, não precisará criar um novo nome para o arquivo de bloqueio. Isso é conveniente.Se o script for chamado por meio de um link simbólico, o bloqueio estará no arquivo real, e não no link.
(Obviamente, se algum processo executa o script e atribui a ele um valor inventado como o argumento zeroth em vez do caminho real, isso é interrompido. Mas isso raramente é feito.)
Você tem certeza de que foi por causa do arquivo usado, e não apenas variação aleatória? Como em um pipeline, não há realmente nenhuma maneira de ter certeza de em que ordem os comandos serão executados
cmd1 & cmd
. Depende principalmente do agendador do SO. Eu recebo variação aleatória no meu sistema.Parece que é assim que o próprio shell mantém uma cópia da descrição do arquivo que contém o bloqueio, em vez de apenas o
flock
utilitário que o contém. Um bloqueio feito comflock(2)
é liberado quando os descritores de arquivo que o possuem são fechados.flock
possui dois modos, para bloquear um bloqueio com base no nome de um arquivo e executar um comando externo (nesse caso,flock
contém o descritor de arquivo aberto necessário) ou pegar um descritor de arquivo de fora, portanto, um processo externo é responsável por manter isto.Observe que o conteúdo do arquivo não é relevante aqui e não há cópias feitas. O redirecionamento para o subshell não copia dados em si, apenas abre um identificador para o arquivo.
Sim, mas o bloqueio está no arquivo , não no descritor do arquivo. Somente uma instância aberta do arquivo pode reter o bloqueio por vez.
Eu acho que você deve conseguir fazer o mesmo sem o subshell, usando
exec
para abrir um identificador para o arquivo de bloqueio:fonte
{ }
vez de( )
também funcionaria e evitaria o subshell.exec
.Um bloqueio de arquivo é anexado a um arquivo, por meio de uma descrição do arquivo . Em um nível alto, a sequência de operações em uma instância do script é:
Manter o bloqueio impede que outra cópia do mesmo script seja executada, porque é isso que os bloqueios fazem. Desde que exista um bloqueio exclusivo em um arquivo em algum lugar do sistema, é impossível criar uma segunda instância do mesmo bloqueio, mesmo através de uma descrição de arquivo diferente.
Abrir um arquivo cria uma descrição do arquivo . Este é um objeto do kernel que não tem muita visibilidade direta nas interfaces de programação. Você acessa uma descrição do arquivo indiretamente por meio de descritores de arquivos, mas normalmente pensa nisso como acessando o arquivo (lendo ou gravando seu conteúdo ou metadados). Um bloqueio é um dos atributos que são uma propriedade para a descrição do arquivo, em vez de um arquivo ou um descritor.
No início, quando um arquivo é aberto, a descrição do arquivo possui um único descritor de arquivo, mas mais descritores podem ser criados criando outro descritor (a
dup
família de chamadas do sistema) ou bifurcando um subprocesso (após o qual o pai e o criança tem acesso à mesma descrição do arquivo). Um descritor de arquivo pode ser fechado explicitamente ou quando o processo em que ele está morre. Quando o último descritor de arquivo anexado a um arquivo é fechado, a descrição do arquivo é fechada.Veja como a sequência de operações acima afeta a descrição do arquivo.
<$0
abre o arquivo de script no subshell, criando uma descrição do arquivo. Neste ponto, há um descritor de arquivo único anexado à descrição: descritor número 0 no subshell.flock
e aguarda a saída. Enquanto o flock está em execução, há dois descritores anexados à descrição: número 0 no subshell e número 0 no processo do flock. Quando o flock assume o bloqueio, isso define uma propriedade da descrição do arquivo. Se outra descrição do arquivo já tiver um bloqueio no arquivo, o rebanho não poderá aceitá-lo, pois é um bloqueio exclusivo.A razão pela qual o script usa um redirecionamento
$0
é que o redirecionamento é a única maneira de abrir um arquivo no shell, e manter um redirecionamento ativo é a única maneira de manter um descritor de arquivo aberto. O subshell nunca lê de sua entrada padrão, apenas precisa mantê-lo aberto. Em um idioma que fornece acesso direto à chamada aberta e fechada, você pode usarVocê pode realmente obter a mesma sequência de operações no shell se fizer o redirecionamento com o
exec
builtin.O script poderia usar um descritor de arquivo diferente se quisesse continuar acessando a entrada padrão original.
ou com um subshell:
O bloqueio não precisa estar no arquivo de script. Pode estar em qualquer arquivo que possa ser aberto para leitura (portanto, ele deve existir, deve ser um tipo de arquivo que possa ser lido, como um arquivo regular ou um pipe nomeado, mas não um diretório, e o processo de script deve ter a permissão para lê-lo). O arquivo de script tem a vantagem de garantir sua presença e legibilidade (exceto no caso de borda em que foi excluído externamente entre o momento em que o script foi chamado e o momento em que o script chega ao
<$0
redirecionamento).Desde que seja
flock
bem-sucedido, e o script esteja em um sistema de arquivos em que os bloqueios não sejam com erros (alguns sistemas de arquivos de rede como o NFS podem ser com erros), não vejo como o uso de um arquivo de bloqueio diferente pode permitir uma condição de corrida. Suspeito de um erro de manipulação da sua parte.fonte
O arquivo usado para bloquear não é importante, o script usa
$0
porque é um arquivo conhecido por existir.A ordem na qual os bloqueios são obtidos será mais ou menos aleatória, dependendo da rapidez com que sua máquina é capaz de iniciar as duas tarefas.
Você pode usar qualquer descritor de arquivo, não necessariamente 0. O bloqueio é mantido no arquivo aberto para o descritor de arquivo, não o próprio descritor.
fonte