`BrokenPipeError`がパイプストリームのサイズに依存するのはなぜですか?

`BrokenPipeError`がパイプストリームのサイズに依存するのはなぜですか?

BrokenPipeError: [Errno 32] Broken pipe次のスクリプトは、同様のコマンドでパイプ処理されたときに発生しますhead(ヘッダーの行数が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

私の理解(fromこの回答そしてこの質問に対する答え)は、Pythonプロセスが書き込みを完了する前にパイプが閉じられるとエラーが発生することです。

ただし、反復範囲を1から16385に減らすとエラーは発生しません(このしきい値がすべてのシステムで同じかどうかはわかりません。再現するには、上または下の数字を試してください)。私は最初にこれがパイプバッファサイズに関連していると思いましたが、私にとっては64Kでした(M=0; while printf A; do >&2 printf "\r$((++M)) B"; done | sleep 999.soによるとそれは原因ではないようです)。

BrokenPipeErrorなぜパイプのサイズによって状況が異なりますか?

これはLinux 5.4.15-arch1-1のPython 3.8.1です。

答え1

パイプサイズによってBrokenPipeErrorが発生するのはなぜですか?

これは、より多くのコンテンツを作成するのに時間がかかり、Pythonの作成が​​完了する前にパイプの右側が死ぬ可能性があるためです。また、Pythonがパイプバッファが保持できるよりも多く書き込もうとするとブロックされて終了するのにhead -1十分な時間を与えます。

生きて死ぬのには時間がかかるので、head -1Pythonはその時間を使ってすべてを書いて(パイプバッファに合った場合)正常に終了することができます。カーネルは、パイプラインの両側を任意の順序で自由に予約でき、head -1適切であると判断した場合は開始を遅らせたり、いつでも停止できるため、これを予測することは困難です。

>>> 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!

しかし、Pythonがパイプに入れることができるよりも多くを書こうとすると、時間がどれだけ長くても必然的にEPIPEORで終わることになります。SIGPIPE

>>> 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

しかし、それは私にとって64Kです...だからそれは原因ではないようです。

Pythonが使用していることを覚えておいてください完全にバッファリング出力が端末でない場合は、1行ずつまたはバイト単位で記録されず、チャンクとして記録されます。一部サイズ:

>>> 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 +++

関連情報