Debug mode and common bugs

asyncio · Expert · 12 min

Async bugs are often silent; debug mode turns them into visible warnings with tracebacks.

Enable debug mode with PYTHONASYNCIODEBUG=1, python -X dev, or asyncio.run(main(), debug=True). It then:

  • logs callbacks or task steps that take longer than loop.slow_callback_duration (default 0.1 s), pointing straight at blocking code,
  • records where each coroutine and task was created, so "coroutine was never awaited" and "Task exception was never retrieved" messages show the creation traceback,
  • raises an error when non-threadsafe loop methods are called from another thread,
  • logs slow I/O selector calls.

Turn on the asyncio logger (logging.basicConfig(level=logging.DEBUG)) to see these messages, and -X dev or -W default to see ResourceWarnings for unclosed transports.

The usual suspects it reveals:

  • forgotten await,
  • blocking calls inside coroutines,
  • fire-and-forget tasks whose exceptions nobody reads,
  • objects (Lock, Queue) created in one loop and used in another, often between test cases.

At runtime, asyncio.all_tasks() lists live tasks, and task.print_stack() shows where each is suspended, handy for diagnosing hangs. A custom handler via loop.set_exception_handler can route unhandled errors to your error tracker.

Example

import asyncio, logging, time
logging.basicConfig(level=logging.WARNING, format="%(name)s: %(message).60s")

async def blocker():
    time.sleep(0.15)                     # blocking call in a coroutine

async def main():
    loop = asyncio.get_running_loop()
    loop.slow_callback_duration = 0.1
    await asyncio.create_task(blocker(), name="blocker")
    print("live tasks:", [t.get_name() for t in asyncio.all_tasks()])

asyncio.run(main(), debug=True)
# asyncio: Executing <Task finished name='blocker' coro=<blocker() don...
# live tasks: ['Task-1']

Remember

  • debug=True / PYTHONASYNCIODEBUG=1 / -X dev turn on debug mode
  • It logs slow callbacks, never-awaited coroutines with creation tracebacks
  • all_tasks() and task.print_stack() help diagnose hangs

Watch out for

  • Leaving debug mode on in production; it adds noticeable overhead.

Check yourself

  1. Debug mode logs 'Executing <Task ...> took 0.150 seconds'. What does it most likely mean?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/asyncio-dev.html

Builds on

Unlocks

Nothing builds on this yet.