Adding a low level wait for file descriptor

I had to integrate using asyncio a blocking library that uses sockets.

Typical use case:

async def handler(fd):
    loop = asyncio.get_running_loop()
    while True:
        await loop.wait_readable(fd)
        x = blocking_read_in_library()
        await long_processing(x)

I ended up using the loop.add_reader and loop.remove_reader functions, which is the only approach that seems to work with file descriptors. It’s a bit more error prone but works well. I don’t want to have repeated call to the blocking read if more data is available as anyway the long_processing happens in the background.

I believe that we could add these two functions instead:

async def wait_readable(loop, fd):
    event = asyncio.Event()
    loop.add_reader(fd, lambda: event.set())

    try:
        await event.wait()

    finally:
        loop.remove_reader(fd)

The function wait_writable could be implemented similarly. These are very simple functions to implement (maybe they shouldn’t be in stdlib) but I felt they could be useful for others.

1 Like

I’d like to see one as well. Right now I have the rather ugly workaround:

async def wait_until_readable(sock, timeout=1e6):
    """Wait until socket is readable, but at most timeout seconds.
    Returns False on timeout, else True."""
    loop = asyncio.get_running_loop()
    future = loop.create_future()

    def callback():
        if not future.cancelled():
            future.set_result(None)

    loop.add_reader(sock, callback)
    try:
        try:
            await asyncio.wait_for(future, timeout=timeout)
        finally:
            loop.remove_reader(sock)
    except asyncio.TimeoutError:
        pass
    return future.done()

may be related to Proposal: Adopt proven anyio/Trio patterns natively into asyncio (multi-release roadmap)

Both trio and anyio support wait_readable and wait_writable:

[1] Introspecting and extending Trio with trio.lowlevel — Trio 0.32.0+dev documentation
[2] API reference — AnyIO 4.12.0.post14 documentation