Por que um pipe “trava”: buffering
- Descrição do problema: ao executar o comando
tail -f /some/log/file | grep thing1 | grep thing2 para encontrar uma saída específica em um arquivo de log, pode acontecer de nada aparecer quando novas linhas são adicionadas lentamente ao log. Parece que o pipe travou, mas na prática o programa não está escrevendo os dados no pipe.
A causa do buffering
- Por que existe buffering: é comum que programas façam buffering antes de escrever dados em um pipe ou arquivo. Isso melhora o desempenho, porque em vez de gravar cada saída imediatamente, o programa junta uma certa quantidade de dados e escreve tudo de uma vez.
- Exemplo:
grep thing1 pode guardar os dados correspondentes até acumular 8KB, e por isso a saída pode não aparecer.
Ao escrever no terminal, não faz buffering
- Diferença entre terminal e pipe: o
grep usa line buffering quando a saída vai para o terminal, mas usa block buffering quando a saída vai para um pipe. Isso é decidido pela função isatty.
Comandos que fazem buffering e os que não fazem
- Comandos que não fazem buffering:
tail, cat, tee etc. não fazem buffering.
- Comandos que fazem buffering:
grep, sed, awk, tcpdump, jq, tr, cut etc. fazem buffering, e alguns permitem desativá-lo com flags específicas.
Buffering padrão de saída em linguagens de programação
- Linguagens que fazem buffering: C, Python, Ruby, Perl etc. fazem buffering na saída por padrão, mas isso pode ser desativado de maneiras específicas.
Perda do conteúdo do buffer ao pressionar Ctrl-C
- Descrição do problema: ao pressionar
Ctrl-C, o conteúdo que está no buffer se perde. Isso acontece porque o sinal SIGINT é entregue primeiro.
- Solução: encontre o PID do
tcpdump e execute kill -TERM $PID para forçar o flush do buffer.
Também há buffering ao redirecionar para arquivo
- Redirecionamento para arquivo: ao redirecionar para um arquivo também ocorre buffering, mas o problema de perda do buffer causado por
Ctrl-C não acontece.
Várias maneiras de evitar buffering
- Solução 1: executar um programa que termine rapidamente.
- Solução 2: usar a flag
--line-buffered do grep.
- Solução 3: usar
awk.
- Solução 4: usar
stdbuf.
- Solução 5: usar
unbuffer.
Variáveis de ambiente para desativar buffering
- Ideia: seria bom existir uma variável de ambiente padrão como
PYTHON_UNBUFFERED. É sugerida uma variável como NO_BUFFER.
Conteúdo omitido
- Tópicos omitidos: a diferença entre line buffering e buffering totalmente desativado, a diferença de buffering entre stderr e stdout, o buffering do driver de TTY do sistema operacional etc.
1 comentários
Comentários do Hacker News
O acesso com buffer precisa fazer flush após um certo número de bytes ou após um certo tempo. É uma forma comum de resolver problemas parecidos em interfaces de hardware
Quando a CPU do sistema inteiro fica ociosa, eu gostaria de descarregar todos os buffers
Trabalho com sistemas NIX há mais de 20 anos, mas sempre esqueço dos problemas de buffering
Uso Unix há mais de 35 anos, mas nunca entendi completamente como o buffering funciona. Essa explicação foi útil
As pessoas estão confundindo "sem buffer" com "buffer por linha"
Buffers existem porque escrever no buffer é relativamente muito mais lento do que imprimir saída na tela
Ao pressionar Ctrl-C, o conteúdo do buffer pode ser perdido
Já tive problemas de buffering no Unix, e nem todas as implementações de
awkse comportam da mesma formaSinto que deixei passar a piada do pipe congelado