Estou cuidando de um programa "master" que gerencia um conjunto de subprocessos "escravos" em execução concorrente. Os subprocessos são iniciados e eliminados conforme necessário. Muitos desses subprocessos usam scripts de inicialização.
A saída de pstree
se parece com isso (trecho, o mestre é implementado em Java, dois escravos lançados via script):
systemd───java─┬─sh───slave
├─slave
└─sh───slave
Anteriormente, os scripts de inicialização redirecionavam as saídas do escravo para arquivos de log. Foi decidido que o mestre também deveria lidar com as saídas do escravo. A implementação do mestre foi estendida adicionando um leitor em buffer como este:
process = Runtime.getRuntime().exec(cmd);
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
while (null != (line = br.readLine())) {
// handle slave output here
}
O sistema então desenvolveu sérios problemas com escravos que haviam sido mortos (enviados SIGTERM
) pelo mestre, mas na verdade ainda estavam em execução. Percebi que isso acontecia apenas com escravos que atendiam a dois critérios:
- eles fizeram uso de um script de início
- eles raramente escreviam na saída padrão
Como o mestre não havia matado o escravo, mas apenas seu pai imediato (o interpretador de shell), o escravo agora era de propriedade do init. No meu caso, o systemd parece ser o reaper padrão . pstree
parece com isso:
systemd─┬─java───sh───slave
└─slave
Funcionalmente, resolvi esse problema matando explicitamente toda a família do escravo . Mas ainda me pergunto:
Por que o systemd mata o filho órfão apenas se ele gravar na saída padrão (ou erro) e somente se a saída padrão tiver sido lida anteriormente por outro processo?
A questão é bastante longa como é. Mediante solicitação, posso fornecer um exemplo de código mínimo para reproduzir o comportamento descrito.