Comando flock do GNU / Linux: como atar vários bloqueios

1

Eu tenho vários comandos que gostaria de executar em paralelo, mas somente se não houver conflitos nos recursos que eles acessam. Então decidi usar flock . Cada comando deve ter um bloqueio exclusivo (gravação) e vários bloqueios compartilhados (leitura). Como flock não suporta vários bloqueios, ingenuamente pensei que algo como:

flock -x a flock -s b flock -s c ... <command>

funcionaria. Eu rapidamente descobri que existem condições de corrida com essa abordagem, devido ao fato de que o conjunto de bloqueios não é atômico. Ao lançar:

flock -x a flock -s b <command1> &
flock -x b flock -s a <command2> &

pode ser que os dois bloqueios exclusivos sejam executados simultaneamente e os dois comandos entrem em um impasse porque eles não podem receber seus bloqueios compartilhados.

Existe uma solução alternativa? São outros utilitários de bloqueio que suportam vários bloqueios de forma atômica? Ou devo criar o meu próprio que tenta tomar os bloqueios, libera todos eles depois de um tempo limite, se falhar, e tenta novamente após um atraso aleatório? Ou algo semelhante?

Atualizar aparentemente, classificar os bloqueios por nome resolve o problema:

flock -x a flock -s b <command1> &
flock -s a flock -x b <command2> &

mas quão robusto é isso? Ele evitará deadlocks em todas as situações com qualquer número de comandos, número de bloqueios, nomes de bloqueio e conjuntos de bloqueios por comando (ainda com a restrição de que há exatamente um bloqueio exclusivo por comando)?

    
por Renaud Pacalet 22.11.2017 / 11:27

2 respostas

1

Este é o problema dos filósofos de refeições . Ao classificar os bloqueios, você implementa a solução de hierarquia de recursos .

While the resource hierarchy solution avoids deadlocks, it is not always practical, especially when the list of required resources is not completely known in advance.

Parece que é robusto se você puder classificar seus recursos e ficar com eles.

Uma solução alternativa pode não permitir que flock aguarde indefinidamente e, em seguida, adicionar alguma lógica para detectar casos quando ele é encerrado porque não foi possível bloquear um arquivo e, por exemplo, repita toda a tarefa depois de algum tempo aleatório.

Em man flock pode-se ver:

-n, --nb, --nonblock
Fail (with an exit code of 1) rather than wait if the lock cannot be immediately acquired.

-w, --wait, --timeout seconds
Fail (with an exit code of 1) if the lock cannot be acquired within seconds seconds. Decimal fractional values are allowed.

O problema é: um possível código de saída de 1 pode vir de qualquer flock ou do comando subjacente. Se o seu flock suportar -E para especificar um código de saída personalizado, use-o talvez.

Este é um exemplo simples da abordagem:

while ! flock -n -x file <command> ; do sleep $(($RANDOM%5)) ; done

Você pode usar vários flock -s. Se algum deles não puder bloquear o arquivo, todos os bloqueios serão liberados e toda a linha aguardará em sleep , não em flock ; neste momento, não bloqueará outra linha semelhante executada em paralelo.

    
por 22.11.2017 / 12:04
1

Quando eu estava envolvido em programação em tempo real, eu sempre abominava as soluções de retardo / repetição, embora elas sejam mais fáceis de codificar.

A chave para evitar um impasse é nunca fazer fila para uma segunda trava enquanto segura uma trava. Então, para três arquivos, use algo como: -

while true
do  flock -x a flock -nE 101 -s b flock -nE 102 c Command
    case $? in
    101) flock -s b;;
    102) flock -s c;;
    *)   break;;
done

Os valores de retorno usados em flock -E devem ser valores que nunca são retornados pelo comando e, quando um desses valores é retornado, o script enfileira o recurso bloqueado e, em seguida, repete a chamada original.

Em princípio, não importa em qual ordem os bloqueios são solicitados, mas pode simplificar a codificação para solicitar primeiro o bloqueio exclusivo.

Existe uma solução mais eficiente que evita liberar o bloqueio enfileirado imediatamente antes de solicitá-lo novamente: construa a sequência de execução toda vez, reconstruindo-a em cada falha sem bloqueio, por exemplo, para o retorno a sequência de execução se tornaria:

flock -s b flock -nE 102 flock -nE 100 -x a c Command

(Obviamente, um caso extra para 100) será necessário.)

Em um caso mais geral, os arquivos de bloqueio seriam passados para uma função que salva os arquivos em uma matriz e constrói a cadeia de execução (a sucessão de flock s) e usa a aritmética de parâmetro para selecionar o arquivo na fila quando os bloqueios sem bloqueio falharem.

A codificação para ambos será complexa, especialmente ao permitir espaços incorporados em Command e seus parâmetros, então escolhi o caso simples acima para ilustrar o princípio, que seria perdido na codificação avançada para o caso geral.

    
por 22.11.2017 / 19:04

Tags