Home > Net >  Why does loop.sock_accept(sock) block other co-oroutines in same loop?
Why does loop.sock_accept(sock) block other co-oroutines in same loop?

Time:05-21

In the example below:

    loop = asyncio.get_event_loop()
    loop.create_task(client())  # Does no start until run_server ends
    await server()
    # await f()

... the call to server causes client to be blocked. In particular it get stuck in the call to

   await loop.sock_accept(sock)

and the client() will not start until server() exits. Why?

Replacing await server() with a difference async functions:

    #await server()
    await f()

allows client to run()

The behavior is the same for Python 3.7 .. 3.10

Similarly, we can flip which is added as as a task and which is immediately await. The task fails to run in both:

    if 1:
        loop.create_task(server())  # Does no start until server ends
        await client()
    else:
        loop.create_task(client())  # Does no start until client ends
        await server()

Full example:

import asyncio
import socket

host, port = ('localhost', 15555)
# host, port = ('127.0.0.1', 15555)
ACCEPT_TIMEOUT = 2

async def server():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    print("Creating server")
    sock.bind((host, port))
    sock.listen(8)
    sock.setblocking(False)  # sock_accept asks for non-blockin
    sock.settimeout(ACCEPT_TIMEOUT)

    loop = asyncio.get_event_loop()

    while True:
        print("ACCEPTING CONNECTIONS")
        try:
            client, _ = await loop.sock_accept(sock)
        except socket.timeout:
            print("ACCEPT TIMEOUT")
            return

        print("123 5")
        loop.create_task(handle_client(client))
        print("123 6")

async def client():
    print("Sending .. ")
    while True:
        try:
            tcp_reader, tcp_writer = await asyncio.open_connection(host, port)
            break
        except ConnectionRefusedError:
            print("Refused")
            await asyncio.sleep(1)

    print("Sending .. OK")
    msg = b"Message"
    while True:
        tcp_writer.write(msg)
        tcp_writer.drain()
        r = tcp_reader.read()
        assert r == msg

async def handle_client(client):  # Never reached
    print("Handle client...")
    loop = asyncio.get_event_loop()
    request = None
    while request != 'quit':
        request = (await loop.sock_recv(client, 255))
        await loop.sock_sendall(client, request)
    client.close()

async def f():
    print("f..")
    asyncio.sleep(1)
    print("f..done")

async def main():
    loop = asyncio.get_event_loop()

    if 1:
        loop.create_task(client())  # Does no start until client ends
        await server()
        # await f()
    else:
        loop.create_task(server())  # Does no start until server ends
        await client()
    
if __name__=='__main__':
    asyncio.run(main())

CodePudding user response:

It seems that on Linux sock.settimeout() overrules sock.setblocking(false).

A couple of changes that made your code work for me:

  • Remove sock.settimeout(...) and use asyncio's timeout functionality instead
  • Use context managers for closing the socket properly, to avoid leaving an unclosed socket behind
  • Add the REUSEADDR flag to keep the socket from being stuck in TIME_WAIT state when running the example repeatedly
async def server():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        print("Creating server")
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        sock.bind((host, port))
        sock.listen(8)
        sock.setblocking(False)  # sock_accept asks for non-blockin

        loop = asyncio.get_event_loop()

        while True:
            print("ACCEPTING CONNECTIONS")
            try:
                client, _ = await asyncio.wait_for(loop.sock_accept(sock), timeout=ACCEPT_TIMEOUT)
            except asyncio.TimeoutError:
                print("ACCEPT TIMEOUT")
                return

            print("123 5")
            loop.create_task(handle_client(client))
            print("123 6")
  • Related