¿Por qué `BrokenPipeError` depende del tamaño del flujo entubado?
On febrero 15, 2021 by admin La siguiente secuencia de comandos genera BrokenPipeError: [Errno 32] Broken pipe
, cuando se envía a un comando como head
(a menos que el número de líneas para encabezar exceda el número de líneas impresas por el script de 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
Mi entendimiento (de esta respuesta y las respuestas a esta pregunta ) es que se genera un error si se cierra una tubería antes de que finalice el proceso de Python escribiendo en él.
Sin embargo, si reduzco el rango iterado con uno a 16385, el error no se genera (no estoy seguro de si este umbral es el mismo en todas las máquinas, así que quizás solo intente un número alto y bajo Reproducir). Inicialmente pensé que esto podría estar relacionado con el tamaño del búfer de la tubería, pero eso es 64K para mí (de acuerdo con M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999
, por lo que esa no parece ser la razón.
¿Por qué la ocurrencia BrokenPipeError
depende del tamaño de lo que se está canalizando?
Esto es con Python 3.8.1 en Linux 5.4.15-arch1 -1.
Comentarios
Respuesta
¿Por qué la ocurrenciaBrokenPipeError depende del tamaño de lo que se está canalizando?
Porque escribir más cosas lleva más tiempo, y el lado derecho de la tubería puede morir antes de que su Python haya terminado de escribirlo. Además, si Python intenta escribir más de lo que cabe en el búfer de la tubería, se bloqueará y le dará a head -1
tiempo suficiente para salir.
Dado que head -1
tarda algo de tiempo en vivir y morir, la pitón puede usar ese tiempo para escribir todas sus cosas, si cabe en el búfer de la tubería, y salir con éxito. Eso es difícil de predecir, ya que el kernel es libre de programar ambos lados de la canalización en cualquier orden y puede retrasar el inicio de head -1
siempre que lo considere oportuno, o puede deténgalo en cualquier 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!
Pero si Python intenta escribir más cosas de las que cabe en la tubería, inexorablemente obtendrá un EPIPE
o SIGPIPE
al final, sin importar cuánto tiempo tenga:
>>> 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
pero eso es 64K para mí … así que esa no parece ser la razón.
Tenga en cuenta que Python está usando búfer completo cuando la salida no es una terminal; no escribe línea por línea o byte por byte, sino por fragmentos de algún tamaño:
>>> 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 +++
:
en lugar del comandohead -1
.python3 -c "for i in range(10000): print(i)" | : </dev/tcp/unix.stackexchange.com/https
. Si dobla / triplica el rango, el pitón con falla con Broken Pipe.