Tenho um script que executa um programa que desejo executar apenas como uma instância por script. Por exemplo, tenho um script "a" e um script "b", cada um executando um programa Java. Quero que apenas uma instância do programa Java seja executada a partir do script "a" e apenas uma instância seja executada a partir do script "b". Nesse caso, não posso simplesmente testar se um executável específico está em execução, porque é a JVM. O próprio script também é encerrado após o início do aplicativo, então não posso testar para ver se ele já está em execução.
Posso gravar o PID do processo em execução em um arquivo e testar, mas estou entrando em uma condição de corrida, pois preciso esperar o programa iniciar e então registrar o PID em um arquivo. Se o script de inicialização for executado mais de uma vez muito rápido, acabo com várias instâncias do programa em execução. Há também a rara ocasião em que duas instâncias tentam gravar no arquivo PID ao mesmo tempo, criando outro problema, em que agora tenho um processo órfão do qual não tenho nenhum registro PID.
Qual é um método confiável para bloquear um programa dentro de um script de inicialização, de modo que eu execute apenas uma única instância de um script sem uma condição de corrida?
Observe que isso não se limita especificamente à execução de programas Java. Este foi apenas um exemplo.
Supondo que este seja um Linux típico: isso é feito criando duas unidades de serviço systemd. Os scripts as iniciam com
Não importa quantas vezes os scripts façam essa chamada, a unidade é iniciada apenas uma vez.
Para um script bash, a abordagem que utilizo para bloquear uma invocação de comando é uma variante do terceiro exemplo nas Perguntas Frequentes do Bash (linkado em um comentário). Eu uso um programa de bloqueio bastante comum (
flock
), mas o utilizo de uma forma menos comum. Explicarei os motivos na descrição, após o código de exemplo:Esta é uma seção do script especial que invocará o(s) comando(s) que não devem ter duas cópias em execução ao mesmo tempo. Este script obtém um bloqueio em um arquivo de bloqueio e é isso que bloqueia cópias duplicadas. Se o bloqueio for bem-sucedido, o script pai invoca os comandos sensíveis. Se o bloqueio falhar, ele não invoca os comandos. Isso é chamado de "bloqueio cooperativo" porque o script coopera (não invocando os comandos sensíveis) quando o bloqueio falha.
A parte incomum é usar
flock
para bloquear um descritor de arquivo aberto no arquivo de bloqueio. Essa é a razão da existência do subshell — seu descritor de arquivo 300 é aberto no arquivo de bloqueio, e esse shell executa oflock
comando.Eu uso esse método porque meu script não precisa fazer nada de especial para remover o bloqueio — quando o subshell sai, o descritor de arquivo é fechado e o bloqueio é liberado automaticamente. Algumas outras maneiras de usar
flock
exigem que o script desbloqueie explicitamente o arquivo após a conclusão dos comandos sensíveis. Isso pode ser um problema, pois um erro ou travamento pode deixar o arquivo de bloqueio bloqueado e o script não invocará os comandos. Essa abordagem do descritor de arquivo tende a não deixar bloqueios obsoletos para trás, então o script não precisa de linhas extras para desbloquear, detectar bloqueios obsoletos e desbloqueá-los à força, etc.No entanto, tudo tem suas vantagens e desvantagens. Em alguns casos de uso, pode ser melhor manter o bloqueio após uma falha de comando e impedir que o script automático execute o(s) comando(s) novamente. Você precisa avaliar as necessidades do seu caso de uso e aplicar a solução mais adequada.
Falácia! Você abre o arquivo primeiro no modo que falha quando o arquivo já existe, escreve qualquer coisa (ou nada, na verdade) e depois faz o que quiser apenas uma vez. (O fato de isso envolver a geração de um novo programa é um "isca"; é irrelevante para o problema. Para que você precisa do PID? A única coisa que importa é se você é o único tentando executar o programa.)
No bash, uma maneira simples de conseguir "criar arquivo, SOMENTE se ele não existir, e caso contrário falhar" é