python-3.xsocketstimeoutsettimeoutrecv

python socket recv timeout does not time-out


Synopsis: server hangs on socket.recv() even though a socket.settimeout() was set.

Have a simple socket based server that loops over commands (simple text messages) sent from client(s) until it receives an 'end' command. I simulate a broken client by - making a connection - sending a command - receive the result - exit (never sending the 'end' command to server)

The whole system worked perfectly when the server/client protocol was adhered to, but with the broken client simulation the server is NOT timing out on the recv.

Relevant server code (simplified):

ip = '192.168.77.170'
port = 12345
args = { app specific data }

with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as serverSock:
    serverSock.bind((ip, port))
    serverSock.listen()
    while True:
        sock, addr = serverSock.accept()
        thread = threading.Thread(target=session, args=(sock, args))
        thread.name = "client {}".format(addr)
        thread.daemon = True
        thread.start()

def session(sock, args):
    sess = threading.local()
    sess.sock = sock
    sess.__dict__.update(args)

    print("-" * 60)
    print("Session opened   from={addr}.".format(addr=sock.getpeername()))
    sock.settimeout(10.0)
    while True:
        try:
            print('about to wait on recv', sock.gettimeout())
            cmd = recvString(sock)
            print('got cmd =', cmd)
            if   cmd.startswith('foo'):  doFoo(sess, cmd)
            elif cmd.startswith('bar'):  doBar(sess, cmd)
            elif cmd.startswith('baz'):  doBaz(sess, cmd)
            elif cmd.startswith('end'):  break
            else:
                raise Exception("Protocol Error: bad command '{}'".format(cmd))
        except TimeoutError as err:
            print("Error protocol timeout")
            break
        finally:
            try:
                sock.close()
            except:
                pass
            print("Session closed.")

def recvString(sock):
    buff = bytearray()
    while True:
        b = sock.recv(1)
        if b == b'\x00': break
        buff += b
    return buff.decode() if len(buff) else ''

When running with broken client I get

> about to wait on recv cmd 10.0
> got cmd = foo
> about to wait on recv cmd 10.0

waits forever (and has very high CPU consumption to add insult to injury)

I've RTFM'd thoroughly and multiple times, and looked at other similar SO postings and all indicate that a simple settimeout() should work. Can't see what I'm doing wrong. Any help greatly appreciated.


Solution

  • while True:
        b = sock.recv(1)
        if b == b'\x00': break
        buff += b
    

    If the peer closes the connection then sock.recv(1) will return b'', i.e. no data. This situation is not accounted for. As a result there will be an endless and busy loop where sock.recv(1) will return with b'', only to be called again and immediately return with b'' etc. This also explains the high CPU.