How to prevent BrokenPipeError when doing a flush in Python?
Asked Answered
S

8

60

Question: Is there a way to use flush=True for the print() function without getting the BrokenPipeError?

I have a script pipe.py:

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

I call it like this from a Unix command line:

python3 pipe.py | head -n3000

And it returns:

0
1
2

So does this script:

import sys
for i in range(4000):
    print(i)
    sys.stdout.flush()

However, when I run this script and pipe it to head -n3000:

for i in range(4000):
    print(i, flush=True)

Then I get this error:

    print(i, flush=True)
BrokenPipeError: [Errno 32] Broken pipe
Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored

I have also tried the solution below, but I still get the BrokenPipeError:

import sys
for i in range(4000):
    try:
        print(i, flush=True)
    except BrokenPipeError:
        sys.exit()
Silesia answered 1/11, 2014 at 19:27 Comment(6)
I can't reproduce it on OS X 10.10, trying centOS 6.6 now.Quintilla
I just tried on OS X 10.9.4 and I was not able to reproduce it. I got the error on Ubuntu 12.04.2 LTS. I will try on Linux Mint Qiana.Silesia
All of your scripts break for me, except the first one...Overland
I am unable to reproduce the exception by running your script in Python 3.4.1. Which Python version are you using?Housecarl
I have tried with version 3.4.1 on Mac OS X 10.9.4 and version 3.3.2 on Ubuntu 12.04.2 LTS. I am currently unable to reproduce the error myself. I had to change range(4) to range(4000) and head -n3 to head -n3000 to reproduce the error.Silesia
related: #108683Saturnian
N
68

The BrokenPipeError is normal as said phantom because the reading process (head) terminates and closes its end of the pipe while the writing process (python) still tries to write.

Is is an abnormal condition, and the python scripts receives a BrokenPipeError - more exactly, the Python interpreter receives a system SIGPIPE signal that it catches and raises the BrokenPipeError to allow the script to process the error.

And you effectively can process the error, because in your last example, you only see a message saying that the exception was ignored - ok it is not true, but seems related to this open issue in Python : Python developpers think important to warn user of the abnormal condition.

What really happens is that AFAIK the python interpreter always signals this on stderr, even if you catch the exception. But you just have to close stderr before exiting to get rid of the message.

I slightly changed your script to :

  • catch the error as you did in your last example
  • catch either IOError (that I get in Python34 on Windows64) or BrokenPipeError (in Python 33 on FreeBSD 9.0) - and display a message for that
  • display a custom Done message on stderr (stdout is closed due to the broken pipe)
  • close stderr before exiting to get rid of the message

Here is the script I used :

import sys

try:
    for i in range(4000):
            print(i, flush=True)
except (BrokenPipeError, IOError):
    print ('BrokenPipeError caught', file = sys.stderr)

print ('Done', file=sys.stderr)
sys.stderr.close()

and here the result of python3.3 pipe.py | head -10 :

0
1
2
3
4
5
6
7
8
9
BrokenPipeError caught
Done

If you do not want the extraneous messages just use :

import sys

try:
    for i in range(4000):
            print(i, flush=True)
except (BrokenPipeError, IOError):
    pass

sys.stderr.close()
Nagano answered 4/11, 2014 at 15:20 Comment(2)
Absolutely very cool :) Thank you! Also didn't know you can put exceptions in a tuple. Thanks for showing me that as well. This works for 3.3.2 and 3.4.0 on Mac OS X 10.9.4 and Ubuntu 12.04.2 LTS (all 4 combinations). It seems to work with both BrokenPipeError and IOError on their own; both part of OSError in <a href="docs.python.org/3/library/… exception hierarchy</a>.Silesia
Note that Python 3 adds a message even if you catch the exception!!! Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> #16314821 Tested in Python 3.6.8.Saturnian
H
41

A note on SIGPIPE was added in Python 3.7 documentation, and it recommends to catch BrokenPipeError this way:

import os
import sys

def main():
    try:
        # simulate large output (your code replaces this loop)
        for x in range(10000):
            print("y")
        # flush output here to force SIGPIPE to be triggered
        # while inside this try block.
        sys.stdout.flush()
    except BrokenPipeError:
        # Python flushes standard streams on exit; redirect remaining output
        # to devnull to avoid another BrokenPipeError at shutdown
        devnull = os.open(os.devnull, os.O_WRONLY)
        os.dup2(devnull, sys.stdout.fileno())
        sys.exit(1)  # Python exits with error code 1 on EPIPE

if __name__ == '__main__':
    main()

Importantly, it says:

Do not set SIGPIPE’s disposition to SIG_DFL in order to avoid BrokenPipeError. Doing that would cause your program to exit unexpectedly also whenever any socket connection is interrupted while your program is still writing to it.

Hypogene answered 23/10, 2019 at 6:56 Comment(5)
This seems to be most up-to-date answer and it's also the approach recommended in the official documentation. Tested on Mac OS 10.14 / Python 3.7.5Etom
I wonder if passing flush=True to print would have the same effect as the sys.stdout.flush() line.Welby
What if I don't want to exit?Seldon
Never mind my previous comment. I've posted my own answer below.Subastral
@topkek, I'm not sure what that's for, I just took it out 'cause I didn't want to exit, and definitely not with return code=1.Paprika
S
8

Answer

import sys
for i in range(4000):
    try:
        print(i, flush=True)
    except BrokenPipeError:
        sys.stdout = None

Explanation

Even if you catch the BrokenPipeError exception, it will be thrown again by Python when your program exits and Python tries to flush stdout. By setting stdout to None, Python will not attempt to flush it.

Drawbacks

While Python routines, such as print(), correctly check if stdout is None and will not fail, it is not uncommon to see programs that do not check. If your program attempts to use stdout.write(), or similar, after setting stdout to None, then Python will throw an AttributeError.

Other answers (and why not)

No answer is shorter or simpler than sys.stdout = None, but some of the common answers have significant problems.

/dev/null

The Python developers have their own suggested code for dealing with the BrokenPipeError.

import os
import sys

def main():
    try:
        # simulate large output (your code replaces this loop)
        for x in range(10000):
            print("y")
        # flush output here to force SIGPIPE to be triggered
        # while inside this try block.
        sys.stdout.flush()
    except BrokenPipeError:
        # Python flushes standard streams on exit; redirect remaining output
        # to devnull to avoid another BrokenPipeError at shutdown
        devnull = os.open(os.devnull, os.O_WRONLY)
        os.dup2(devnull, sys.stdout.fileno())
        sys.exit(1)  # Python exits with error code 1 on EPIPE

if __name__ == '__main__':
    main()

While that is the canonical answer, it is rather grotesque in that it needlessly opens a new file descriptor to /dev/null just so that Python can flush it before it is closed.

Why not: For most people, it is pointless. This problem is caused by Python flushing a handle that we have already caught a BrokenPipeError on. We know it will fail, so the solution should be for Python to simply not flush that handle. To allocate a new file descriptor just to appease Python is silly.

Why (maybe): Redirecting stdout to /dev/null may actually be the right solution for some people whose programs, after receiving a BrokenPipeError, will continue manipulating stdout without checking it first. However, that is not the common case.

sys.stderr.close()

Some people have suggested closing stderr to hide the bogus BrokenPipe error message.

Why not: It also prevents any legitimate errors from being shown.

signal(SIGPIPE, SIG_DFL)

Another common answer is to use SIG_DFL, the default signal handler, to cause the program to die whenever a SIGPIPE signal is received.

Why not: SIGPIPE can be sent for any file descriptor, not just stdout, so your entire program would suddenly and mysteriously die if, for example, it was writing to a network socket whose connection gets interrupted.

pipe.py | something | head

One non-python solution is to first pipe stdout to a program that will continue reading data from the Python program even when its own standard output is closed. For example, presuming you have the GNU version of tee, this works:

pipe.py | tee -p /dev/null | head

Why not: The question was looking for an answer in Python. Also, it is suboptimal in that it will keep pipe.py running longer than it needs to, possible consuming significant resources.

Subastral answered 4/11, 2021 at 0:6 Comment(0)
O
4

According to the Python documentation, this is thrown when:

trying to write on a pipe while the other end has been closed

This is due to the fact that the head utility reads from stdout, then promptly closes it.

As you can see, it can be worked around by merely adding a sys.stdout.flush() after every print(). Note that this sometimes does not work in Python 3.

You can alternatively pipe it to awk like this to get the same result as head -3:

python3 0to3.py | awk 'NR >= 4 {exit} 1'

Hope this helped, good luck!

Overland answered 1/11, 2014 at 23:57 Comment(6)
Thanks for this. Unfortunately the awk workaround is not an option. I don't know how many lines the real output produces. I'll leave the question unanswered for a few days, if you don't mind. Thanks again.Silesia
@Silesia Does sys.stdout.flush() not work? Also, with head don't you also have to know how many lines it will be anyway?Overland
@Silesia It is impossible to do what you are asking. There is no workaround to enable head to work with your program in this manner.Overland
@Silesia It wouldn't be, because it's different depending on the program you pipe the output into. The reason this error occurs is because when the program you pipe output into closes before your python script, it closes the output pipe, e.g. stdout. You are better off either manually piping with subprocess or using python's regex capabilities instead of grep.Overland
@Silesia I am focusing on it. I'm telling you it's not possible. I quote The reason this error occurs is because when the program you pipe output into closes before your python script, it closes the output pipe, e.g. stdout.Overland
@Silesia No problem! It is possible but it is not as easy to do, unfortunately :(Overland
P
4

I've often wished there were a command-line option to suppress these signal handlers.

import signal

# Don't turn these signal into exceptions, just die.
signal.signal(signal.SIGINT, signal.SIG_DFL)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)

Instead, the best we can do is uninstall the handlers as soon as possible as the Python script starts running.

Profusion answered 7/9, 2019 at 12:43 Comment(4)
The Python developers state explicitly that one should not do that. docs.python.org/3/library/signal.html#note-on-sigpipeSubastral
If there's some cleanup your program needs to do before exiting, then that's correct -- you wouldn't want SIGINT or SIGPIPE set to SIG_DFL. Also, there are many, many more signals you'd also want to be catching in that case.Profusion
I looked into it more and it appears that SIGPIPE doesn't specify which file descriptor has the broken pipe, so setting it to SIG_DFL may kill the program inappropriately. For example, you could get a SIGPIPE if your Python program is accessing the network and the socket connection is reset.Subastral
Oh, and I discovered that there's a simpler solution. The problem turns out to be Python itself is flushing stdout when it exits, causing a second BrokenPipeError. Just set stdout = None in the exception handler and Python won't do that.Subastral
H
1

As you can see in the output that you had posted the last exception is raised in the destructor phase : that is why you have ignored at the end

Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored

A simple example to understand what's up in that context is the follow:

>> class A():
...     def __del__(self):
...         raise Exception("It will be ignored!!!")
... 
>>> a = A()
>>> del a
Exception Exception: Exception('It will be ignored!!!',) in <bound method A.__del__ of <__builtin__.A instance at 0x7ff1d5c06d88>> ignored
>>> a = A()
>>> import sys
>>> sys.stderr.close()
>>> del a

Every exception that is triggered while the object is destroyed will cause a standard error output that explain the exception occurred and ignored (that is because python will inform you that something could not be correctly handle in destroy phase). Anyway, that kind of exceptions cannot be cached and so you can just remove the calls that can generate it or close stderr.

Come back to the question. That exception is not a real problem (as say it is ignored) but if you don't want print it you must override the the function that can be called when the object will be destroyed or close stderr as @SergeBallesta correctly suggested : in you case you can shutdown write and flush function and no exception will be triggered in destroy context

That is an example of how you can do it:

import sys
def _void_f(*args,**kwargs):
    pass

for i in range(4000):
    try:
        print(i,flush=True)
    except (BrokenPipeError, IOError):
        sys.stdout.write = _void_f
        sys.stdout.flush = _void_f
        sys.exit()
Hanover answered 4/11, 2014 at 13:11 Comment(6)
This is not a solution. He wants to be able to use it with flush=True with print.Overland
As I write i tested it on python 3.2... now I adapt it to python 3.4Iman
@Overland That is the version for flush=True in print... That is a solution isn't it?Iman
This also works. Thank you very much! I chose the answer from @serge-ballesta, because he replied slightly faster and avoided definition of a new function. Thank you both. I have much to learn about exceptions, which I have never sat down and read about.Silesia
@Silesia thank you for the vote. But my answer was the first stackoverflow.com/revisions/26736013/1. It edited it because I wrote it for python 3.2 (I was at work) where nor flush=True and BrokenPipeError wasn't present, but the issue was still present if you try with a lower head. While I revisited it I add an example to explain what really happen when exceptions are raised in destructor to understand better how to catch it. Finally my answer is more specific because by shutting down stderr you can masquerade more issues than the broken pipe related.Iman
@Silesia Anyway I understand your choice because the Sarge Balestra answer is well written and that give a simple way to take a solution.Iman
S
1

Ignore SIGPPIE temporarily

I'm not sure how bad an idea this is, but it works:

#!/usr/bin/env python3

import signal
import sys

sigpipe_old = signal.getsignal(signal.SIGPIPE)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
for i in range(4000):
    print(i, flush=True)
signal.signal(signal.SIGPIPE, sigpipe_old)
Saturnian answered 16/9, 2018 at 7:49 Comment(1)
The Python developers state explicitly that one should not do that. docs.python.org/3/library/signal.html#note-on-sigpipeSubastral
T
1

While others have covered the underlying issue in great detail there is a straightforward workaround:

python whatever.py | tail -n +1 | head -n3000

Explanation: tail buffers until it's STDIN is closed (python quits and closes its STDOUT). So only tail gets the SIGPIPE when head quits. The -n +1 is effectively a no-op, making tail output the "tail" starting at line 1, which is the entire buffer.

Trotskyism answered 31/7, 2019 at 19:17 Comment(1)
Have you tested this with a smaller value for head? It does not work for me.Subastral

© 2022 - 2024 — McMap. All rights reserved.