How to handle a broken pipe (SIGPIPE) in python?

Question:

I’ve written a simple multi-threaded game server in python that creates a new thread for each client connection. I’m finding that every now and then, the server will crash because of a broken-pipe/SIGPIPE error. I’m pretty sure it is happening when the program tries to send a response back to a client that is no longer present.

What is a good way to deal with this? My preferred resolution would simply close the server-side connection to the client and move on, rather than exit the entire program.

PS: This question/answer deals with the problem in a generic way; how specifically should I solve it?

Asked By: Adam Plumb

||

Answers:

Read up on the try: statement.

try:
    # do something
except socket.error, e:
    # A socket error
except IOError, e:
    if e.errno == errno.EPIPE:
        # EPIPE error
    else:
        # Other error
Answered By: S.Lott

SIGPIPE (although I think maybe you mean EPIPE?) occurs on sockets when you shut down a socket and then send data to it. The simple solution is not to shut the socket down before trying to send it data. This can also happen on pipes, but it doesn’t sound like that’s what you’re experiencing, since it’s a network server.

You can also just apply the band-aid of catching the exception in some top-level handler in each thread.

Of course, if you used Twisted rather than spawning a new thread for each client connection, you probably wouldn’t have this problem. It’s really hard (maybe impossible, depending on your application) to get the ordering of close and write operations correct if multiple threads are dealing with the same I/O channel.

Answered By: Glyph

My answer is very close to S.Lott’s, except I’d be even more particular:

try:
    # do something
except IOError, e:
    # ooops, check the attributes of e to see precisely what happened.
    if e.errno != 23:
        # I don't know how to handle this
        raise

where “23” is the error number you get from EPIPE. This way you won’t attempt to handle a permissions error or anything else you’re not equipped for.

Answered By: Kirk Strauser

Assuming that you are using the standard socket module, you should be catching the socket.error: (32, 'Broken pipe') exception (not IOError as others have suggested). This will be raised in the case that you’ve described, i.e. sending/writing to a socket for which the remote side has disconnected.

import socket, errno, time

# setup socket to listen for incoming connections
s = socket.socket()
s.bind(('localhost', 1234))
s.listen(1)
remote, address = s.accept()

print "Got connection from: ", address

while 1:
    try:
        remote.send("message to peern")
        time.sleep(1)
    except socket.error, e:
        if isinstance(e.args, tuple):
            print "errno is %d" % e[0]
            if e[0] == errno.EPIPE:
               # remote peer disconnected
               print "Detected remote disconnect"
            else:
               # determine and handle different error
               pass
        else:
            print "socket error ", e
        remote.close()
        break
    except IOError, e:
        # Hmmm, Can IOError actually be raised by the socket module?
        print "Got IOError: ", e
        break

Note that this exception will not always be raised on the first write to a closed socket – more usually the second write (unless the number of bytes written in the first write is larger than the socket’s buffer size). You need to keep this in mind in case your application thinks that the remote end received the data from the first write when it may have already disconnected.

You can reduce the incidence (but not entirely eliminate) of this by using select.select() (or poll). Check for data ready to read from the peer before attempting a write. If select reports that there is data available to read from the peer socket, read it using socket.recv(). If this returns an empty string, the remote peer has closed the connection. Because there is still a race condition here, you’ll still need to catch and handle the exception.

Twisted is great for this sort of thing, however, it sounds like you’ve already written a fair bit of code.

Answered By: mhawke

I face with the same question. But I submit the same code the next time, it just works.
The first time it broke:

$ packet_write_wait: Connection to 10.. port 22: Broken pipe

The second time it works:

[1]   Done                    nohup python -u add_asc_dec.py > add2.log 2>&1

I guess the reason may be about the current server environment.

Answered By: yuan
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.