Search code examples
pythonperlpiperuntime-errorflush

BrokenPipeError in Python but not in Perl


Consider this Python script:

for i in range(4000):
    print(i)

and this Perl script:

for my $i (0..4000-1) {
    print $i, "\n";
}

python3 pipe.py | head -n3000 >/dev/null produces an error:

Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
BrokenPipeError: [Errno 32] Broken pipe

but

perl pipe.pl | head -n3000 >/dev/null produces no error (in Perl v5.26.1).

Why such a discrepancy between Python and Perl? How to make Perl to produce a similar error message?


Solution

  • What's going on here is that in both cases you have a process writing to a pipe whose read end was closed (by head exiting after a certain number of bytes).

    This causes a SIGPIPE signal to be sent to the writing process. By default this kills the process. The process can ignore the signal if it wants to, which just makes the write call fail with an EPIPE error.

    Starting with version 3.3, Python raises a BrokenPipeError exception in this case, so it looks like Python 1) ignores SIGPIPE by default and 2) translates EPIPE to a BrokenPipeError exception.

    Perl does not ignore or handle signals by default. That means it gets killed by SIGPIPE in your example, but because it is not the last command in a pipeline (that would be head here), the shell just ignores it. You can make it more visible by not using a pipeline:

    perl pipe.pl > >(head -n3000 >/dev/null)
    

    This piece of bash trickery makes perl write to a pipe, but not as part of a shell pipeline. I can't test it now, but at minimum this will set $? (the command exit status) to 141 in the shell (128 + signal number, which for SIGPIPE is 13), and it may also report a Broken pipe.

    You can deal with it manually in the Perl code, though:

    • Variant 1: Throw an error from the signal handler

      $SIG{PIPE} = sub { die "BrokenPipeError" };
      
    • Variant 2: Ignore the signal, handle write errors

      $SIG{PIPE} = 'IGNORE';
      ...
      print $i, "\n" or die "Can't print: $!";
      

      Note that in this case you have to think about buffering, however. If you don't enable autoflush (as in STDOUT->autoflush(1)) and output is going to a pipe or file, Perl will collect the text in an internal buffer first (and the print call will succeed). Only when the buffer gets full (or when the filehandle is closed, whichever happens first) is the text actually written out and the buffer emptied. This is why close can also report write errors.