backend / python core / 25_debug_introspect_async.md

How do you debug and introspect async code?

3 interview angles 2 min read source

How do you debug and introspect async code?

Answer

Breakpoints and stepping

  • Use your IDE’s debugger (e.g. VS Code, PyCharm) with breakpoints. With asyncio, set breakpoints inside async def functions; the debugger will stop when the event loop runs that coroutine. Be aware that stepping over await can jump to other tasks—step carefully or use “run to cursor” to control flow.

asyncio.debug and slow callback detection

  • Run with PYTHONASYNCIODEBUG=1 or asyncio.run(..., debug=True) to enable asyncio debug mode. This adds slow callback detection (warns if a callback blocks the loop too long) and better exception reporting. Helpful for finding blocking calls or long-running synchronous code in async paths.

Logging and tracing

  • Add logging (especially around await points and task creation) with correlation IDs or task names so you can trace a request across tasks. Use asyncio.current_task() or task.get_name() to identify which task is running. Structured logging (JSON) with timestamps helps replay async flows.

Exception groups (Python 3.11+)

  • asyncio.gather(..., return_exceptions=False) and similar APIs can raise ExceptionGroup when multiple tasks fail. Use except* to catch and handle them: except* TimeoutError: ... or iterate eg.exceptions to inspect each failure. Log the full group so you don’t lose secondary errors.

Introspection: inspect running tasks

  • Use asyncio.all_tasks() to see all tasks in the event loop; filter by name or coroutine if needed. Use task.get_stack() or task.get_coro() to inspect a task’s state. Use inspect.iscoroutine() / inspect.iscoroutinefunction() to check if something is a coroutine before awaiting.

Avoid blocking the event loop

  • If you block in an async function (e.g. time.sleep, CPU-heavy work, sync I/O), the whole loop stalls. Use await asyncio.sleep() instead of time.sleep(), run CPU work in run_in_executor(), and use async I/O (e.g. aiohttp, aiosqlite). Debugger stepping can also “block” briefly—acceptable for debugging, but avoid long pauses in production paths.

Interview angle

  • “How do you debug async code?” - asyncio debug mode surfaces slow callbacks and un-awaited coroutines; asyncio.all_tasks() shows what’s currently scheduled; and per-task naming makes those dumps readable. A stuck service usually means a task blocked on something that never completes.
  • “How do you find a blocked event loop?” - debug mode logs callbacks exceeding a threshold. The external symptom is p99 latency rising on every endpoint at once, including ones doing nothing.
  • “Why do exceptions sometimes vanish in async code?” - a task nobody awaits swallows its exception until garbage collection. Keep a reference and attach a done-callback, or own the task in a TaskGroup.