how to use slow_callback_duration to find blocking coroutines in asyncio

Viewed 207

I'm debugging some code where one coroutine is, I think, blocking another coroutine causing it to time out. I don't know where that is. I believe this toy code models my situation, or at least, a situation I want to understand:

import asyncio
import logging
import sys
import time
import warnings

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s - %(levelname)7s: %(message)s",
    stream=sys.stderr,
)
logger = logging.getLogger("")
LOG = logger


async def timeout_checker():
    logger.info("starting timeout clock")
    t0 = time.time()
    await asyncio.sleep(0.1)
    if time.time() - t0 > 3:
        raise asyncio.TimeoutError("timeout!")


async def selfish_sleeper():
    logger.info("selfish sleeping")
    time.sleep(4)
    logger.info(f"done sleeping")


async def two_level_sleeper():
    await selfish_sleeper()


async def timeshare():
    task = asyncio.create_task(timeout_checker())

    logger.info(f"created task, starting unselfish sleep")
    await asyncio.sleep(0.05)

    # await selfish_sleeper()
    await two_level_sleeper()

    result = await task
    logger.info(f"result {result}")


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

    # Enable debugging
    event_loop.set_debug(True)

    # Make the threshold for "slow" tasks very very small for
    # illustration. The default is 0.1, or 100 milliseconds.
    event_loop.slow_callback_duration = 0.001

    # Report all mistakes managing asynchronous resources.
    warnings.simplefilter("always", ResourceWarning)

    await timeshare()


if __name__ == "__main__":
    logger.info("start")
    asyncio.run(main(), debug=True)
    logger.info("done")

In this toy, I was hoping to find something that could just tell me that selfish_sleeper is taking more time than expected. When I run this, I get:

2021-11-29 14:16:51,439 -    INFO: start
2021-11-29 14:16:51,440 -   DEBUG: Using selector: EpollSelector
2021-11-29 14:16:51,464 -    INFO: created task, starting unselfish sleep
2021-11-29 14:16:51,468 - WARNING: Executing <Task pending name='Task-1' coro=<main() running at /mnt/c/Users/me/Documents/mydir/selfish.py:60> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/base_events.py:424> cb=[_run_until_complete_cb() at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/base_events.py:184] created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:638> took 0.012 seconds
2021-11-29 14:16:51,468 -    INFO: starting timeout clock
2021-11-29 14:16:51,473 - WARNING: Executing <Task pending name='Task-2' coro=<timeout_checker() running at /mnt/c/Users/me/Documents/mydir/selfish.py:19> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/base_events.py:424> created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:337> took 0.004 seconds
2021-11-29 14:16:51,521 - WARNING: Executing <TimerHandle when=24730.752979659 _set_result_unless_cancelled(<Future finis...events.py:424>, None) at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/futures.py:308 created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:605> took 0.003 seconds
2021-11-29 14:16:51,521 -    INFO: selfish sleeping
2021-11-29 14:16:55,526 -    INFO: done sleeping
2021-11-29 14:16:55,526 - WARNING: Executing <Task pending name='Task-1' coro=<main() running at /mnt/c/Users/me/Documents/mydir/selfish.py:60> wait_for=<Task pending name='Task-2' coro=<timeout_checker() running at /mnt/c/Users/me/Documents/mydir/selfish.py:19> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/base_events.py:424> cb=[Task.task_wakeup()] created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:337> cb=[_run_until_complete_cb() at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/base_events.py:184] created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:638> took 4.005 seconds
2021-11-29 14:16:55,531 - WARNING: Executing <TimerHandle when=24730.806657506997 _set_result_unless_cancelled(<Future finis...events.py:424>, None) at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/futures.py:308 created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:605> took 0.004 seconds
2021-11-29 14:16:55,535 - WARNING: Executing <Task finished name='Task-2' coro=<timeout_checker() done, defined at /mnt/c/Users/me/Documents/mydir/selfish.py:16> exception=TimeoutError('timeout!') created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:337> took 0.004 seconds
2021-11-29 14:16:55,539 - WARNING: Executing <Task finished name='Task-1' coro=<main() done, defined at /mnt/c/Users/me/Documents/mydir/selfish.py:47> exception=TimeoutError('timeout!') created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:638> took 0.003 seconds
2021-11-29 14:16:55,548 - WARNING: Executing <Task finished name='Task-3' coro=<BaseEventLoop.shutdown_asyncgens() done, defined at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/base_events.py:530> result=None created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:638> took 0.003 seconds
2021-11-29 14:16:55,555 - WARNING: Executing <Task finished name='Task-4' coro=<BaseEventLoop.shutdown_default_executor() done, defined at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/base_events.py:555> result=None created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:638> took 0.002 seconds
2021-11-29 14:16:55,560 -   DEBUG: Close <_UnixSelectorEventLoop running=False closed=False debug=True>
Traceback (most recent call last):
  File "/mnt/c/Users/me/Documents/mydir/selfish.py", line 65, in <module>
    asyncio.run(main(), debug=True)
  File "/home/me/miniconda3/envs/py310/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/home/me/miniconda3/envs/py310/lib/python3.10/asyncio/base_events.py", line 641, in run_until_complete
    return future.result()
  File "/mnt/c/Users/me/Documents/mydir/selfish.py", line 60, in main
    await timeshare()
  File "/mnt/c/Users/me/Documents/mydir/selfish.py", line 43, in timeshare
    result = await task
  File "/mnt/c/Users/me/Documents/mydir/selfish.py", line 21, in timeout_checker
    raise asyncio.TimeoutError("timeout!")
asyncio.exceptions.TimeoutError: timeout!

where the message showing the excess time is:

2021-11-29 14:16:55,526 - WARNING: Executing <Task pending name='Task-1' coro=<main() running at /mnt/c/Users/me/Documents/mydir/selfish.py:60> wait_for=<Task pending name='Task-2' coro=<timeout_checker() running at /mnt/c/Users/me/Documents/mydir/selfish.py:19> wait_for=<Future pending cb=[Task.task_wakeup()] created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/base_events.py:424> cb=[Task.task_wakeup()] created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:337> cb=[_run_until_complete_cb() at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/base_events.py:184] created at /home/me/miniconda3/envs/py310/lib/python3.10/asyncio/tasks.py:638> took 4.005 seconds

Seems completely misleading: it seems to say that the time was spent in either main (which is too high level to be useful) or in timeout_checker, which isn't the case. I don't see any hint in that message for which coroutine actually consumed the time.

I'm sure a profiler (e.g., yappi) could find it, but I'm not sure I can use that in the context where the problem is arising. I was hopeful that slow_callback_duration would be sufficient. Am I using it incorrectly or misinterpreting what it's telling me?

0 Answers
Related