Timeout for python coroutines

When the server closes the connection, sock_recv returns an empty bytearray (b''), indicating end of file. Since you don’t handle that condition, your code ends up stuck in an infinite loop processing the same buffer.

To correct it, add something like:

if data == b'':
    break

…after the data = await loop.sock_recv(...) line.

But the above still doesn’t explain why wait_for is unable to cancel the rogue coroutine. The problem is that await doesn’t mean “pass control to the event loop”, as it is sometimes understood. It means “request value from the provided awaitable object, yielding control to the event loop if (and as long as) the object indicates that it does not have a value ready.” The if is crucial: if the object does have a value ready when first asked, this value will be used immediately without ever deferring to the event loop. In other words, await doesn’t guarantee that the event loop will get a chance to run.

For example, the following coroutine completely blocks the event loop and prevents any other coroutine from ever running, despite its inner loop consisting of nothing but awaiting:

async def busy_loop():
    while True:
        await noop()

async def noop():
    pass

In your example, since the socket does not block at all when it is at end-of-file, the coroutine is never suspended, and (in collusion with the above bug) your coroutine never exits.

To ensure that other tasks get a chance to run, you can add await asyncio.sleep(0) in a loop. This should not be necessary for most code, where requesting IO data will soon result in a wait, at which point the event loop will kick in. (In fact, needing to do so often indicates a design flaw.) In this case it is only in combination with the EOF-handling bug that the code gets stuck.

Leave a Comment