Handling socket errors when making non-blocking select() or event loop servers in Python is important, since otherwise your program simply won’t work. If it doesn’t make the distinction between socket errors that cause a disconnect, or socket errors that mean you simply need to wait, your program will be incorrect. It may kill connections or no reason, it might crash entirely, or worse things may happen depending on your context.
The quick solution, up front, is to check the “errno” module against the socket error code. The code sample below will demonstrate how to handle WSAEWOULDBLOCK in Python, along with other OS-dependent errors:
import errno
import socket
def is_unready_error_code(errc):
if hasattr(errno, 'EAGAIN'):
return errc == errno.EAGAIN
elif hasattr(errno, 'WSAEWOULDBLOCK'):
return errc == errno.WSAEWOULDBLOCK
elif hasattr(errno, 'EWOULDBLOCK'):
return errc == errno.EWOULDBLOCK
return False
class ClientSocket(object):
def __init__(self, conn):
self.conn = conn
self.buffer = b''
def handle_recv(self):
try:
data = self.conn.recv(1024)
while data:
self.buffer += data
data = self.conn.recv(1024)
except OSError as e:
# First, check if it's an error code that means the socket no longer has any data buffered from the network
if is_unready_error_code(e.args[0]):
self.process_data()
else:
print('Error reading from socket', e.args[0])
self.conn.shutdown()
def main():
# see GitHub repo for the complete file
listen_socket_loop_goes_here()
if __name__ == '__main__':
main()
Understanding The Code
Let’s talk about is_unready_error_code
. In ClientSocket.handle_recv
, we have an except block corresponding to OSError, which all socket exception subclasses derive from. We take the first argument (e.args[0]), as it represents the integer constant the low-level C API returns from the operating system, and then compare it to errors that don’t interfere with our connection by calling is_unready_error_code
with this value.
In this function, we check if errno has the EAGAIN constant defined and if so we compare errc to it. Then we do the same with the WSAEWOULDBLOCK constant, then the EWOULDBLOCK constant.
Why do we do it this way?
Well, Python will have different constants defined on the errno module on different operating systems. It wouldn’t make sense to check your error code against WSAEWOULDBLOCK on MacOS or on Linux, because it could cause errors from confusing whatever integer value WSAEWOULDBLOCK is with whatever that integer value means on another operating system.
For example, maybe the integer represented by WSAEWOULDBLOCK (in practice, this is defined as 10035 by the Winsock API) means something else on Linux. If an error code returned from the socket API on Linux is also defined as 10035, your program could see this as WSAEWOULDBLOCK and continue trying to poll the socket with select() when actually it means “STOP_READING_SOCKET_OR_COMPUTER_WILL_EXPLODE”- you can see in this silly example how things might go wrong.
Put any questions you have in the comments, and check our overall series on using event loops in Python.