Por que `BrokenPipeError` depende do tamanho do fluxo canalizado?
On Fevereiro 15, 2021 by admin O script a seguir gera BrokenPipeError: [Errno 32] Broken pipe
, quando canalizado para um comando como head
(a menos que o número de linhas para o cabeçalho exceda o número de linhas impressas pelo script Python).
for i in range(16386): print("")
$ python test-pipe.py | head -1 Traceback (most recent call last): File "test-pipe.py", line 2, in <module> print("") BrokenPipeError: [Errno 32] Broken pipe
Meu entendimento (de esta resposta e as respostas a esta pergunta ) é que um erro é gerado se um tubo for fechado antes de o processo Python terminar escrevendo nele.
No entanto, se eu decrementar o intervalo iterado de um para 16385, o erro não é gerado (não tenho certeza se este limite é o mesmo em todas as máquinas, então podebce apenas tentar um número alto e baixo reproduzir). Inicialmente, pensei que isso pudesse estar relacionado ao tamanho do buffer do pipe, mas isso é 64 K para mim (de acordo com M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999
, então esse não parece ser o motivo.
Por que a ocorrência BrokenPipeError
depende do tamanho do que está sendo canalizado?
Isso é com Python 3.8.1 no Linux 5.4.15-arch1 -1.
Comentários
Resposta
Por que o occurBrokenPipeError depende do tamanho do que está sendo canalizado?
Porque escrever mais coisas leva mais tempo, e o lado direito do pipeline pode morrer antes que seu python termine de escrevê-lo. Além disso, se o python tentar escrever mais do que cabe no buffer do pipe, ele bloqueará e dará ao head -1
tempo suficiente para sair.
Já que head -1
leva algum tempo para viver e morrer, o python pode usar esse tempo para escrever todas as suas coisas – se couber no buffer do pipe – e sair com sucesso. Isso é difícil de prever, já que o kernel está livre para agendar ambos os lados do pipeline em qualquer ordem e pode atrasar o início do head -1
pelo tempo que achar adequado, ou pode pare a qualquer momento.
>>> python3 -c "for i in range(50000): print("")" | sleep .01 Traceback (most recent call last): File "<string>", line 1, in <module> BrokenPipeError: [Errno 32] Broken pipe >>> python3 -c "for i in range(50000): print("")" | sleep .1 # OK!
Mas se o python tentar escrever mais coisas do que cabem no tubo, inevitavelmente obterá um EPIPE
ou SIGPIPE
no final, não importa quanto tempo tenha:
>>> python3 -c "for i in range(100000): print("")" | sleep 20 Traceback (most recent call last): File "<string>", line 1, in <module> BrokenPipeError: [Errno 32] Broken pipe
mas isso é 64K para mim … então esse não parece ser o motivo.
Lembre-se de que o python está usando buffer completo quando a saída não é um terminal; ele não escreve linha por linha ou byte por byte, mas por pedaços de algum tamanho:
>>> strace -s3 -e trace=write python3 -c "for i in range(50000): print("")" | sleep .3 write(1, "\n\n\n"..., 8193) = 8193 write(1, "\n\n\n"..., 8193) = 8193 write(1, "\n\n\n"..., 8193) = 8193 write(1, "\n\n\n"..., 8193) = 8193 write(1, "\n\n\n"..., 8193) = 8193 write(1, "\n\n\n"..., 8193) = 8193 write(1, "\n\n\n"..., 842) = 842 +++ exited with 0 +++
:
em vez do comandohead -1
.python3 -c "for i in range(10000): print(i)" | : </dev/tcp/unix.stackexchange.com/https
. Se você dobrar / triplicar o intervalo, o python com falha com tubo quebrado.