asyncio
debugging
GDB
Python
coroutines

How to debug asyncio coroutines with GDB?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Using gdb to debug asyncio code is usually the right move when the Python process is crashing, hanging inside native code, or stuck in a way that ordinary logging cannot explain. It is not the first tool for normal coroutine logic bugs, but it becomes valuable when you need to inspect the CPython runtime, C extensions, or thread state underneath the event loop.

Know When gdb Is the Right Tool

For ordinary async mistakes such as a forgotten await, cancelled task, or bad timeout logic, Python-level tools are usually faster:

  • asyncio debug mode
  • task introspection
  • logging
  • 'faulthandler'
  • 'pdb or IDE debugging'

Reach for gdb when you have symptoms like:

  • a segmentation fault
  • a deadlock or apparent hard hang
  • a crash in a C extension used by async code
  • a Python process stuck with no useful traceback

That framing matters because gdb operates below the coroutine abstraction.

Start Python Under gdb

A normal entry point looks like this:

bash
gdb --args python app.py

Inside gdb, run the program:

gdb
run

If the process crashes, use a native backtrace first.

gdb
bt

That shows the C-level stack, which is essential when the failure involves the interpreter itself or native modules.

Use Python-Aware gdb Commands When Available

Many Python builds ship helper commands that make gdb much more useful for CPython processes.

Common examples are:

gdb
1py-bt
2py-list
3py-up
4py-down

These commands let you inspect Python frames rather than only raw C frames.

A typical workflow after a crash is:

gdb
bt
py-bt

The first shows where the process is in native code. The second helps map that back to Python-level execution.

Debugging an Event Loop Hang

Async programs often fail by hanging rather than crashing. In that case, attach gdb to the running process.

bash
gdb -p 12345

Then inspect where the process is stopped.

gdb
thread apply all bt
py-bt

This helps answer questions such as:

  • is the event loop blocked in a system call
  • is a worker thread stuck in native code
  • is the interpreter waiting on a lock
  • which Python frame was active when the process stopped making progress

With async code, the real bug is often that some blocking operation leaked into the event loop thread.

Combine gdb With Asyncio-Level Introspection

gdb is strongest when paired with Python-side evidence. For example, you can enable asyncio debug mode to surface scheduling warnings.

python
1import asyncio
2
3async def main():
4    await asyncio.sleep(1)
5
6asyncio.run(main(), debug=True)

If that still does not explain the failure, gdb can tell you whether the process is blocked in native I/O, interpreter internals, or an extension module.

Think of gdb as the tool that answers "what is the process doing underneath the coroutine abstraction?"

Breakpoints and Native Boundaries

You can set breakpoints in C functions if you know where the problem likely lives, such as a C extension callback or CPython runtime function. That is advanced usage, but it becomes relevant when a coroutine awaits something that eventually crosses into native code.

For most Python developers, though, the most useful commands are still:

  • 'run'
  • 'bt'
  • 'thread apply all bt'
  • 'py-bt'

Those commands already go a long way toward explaining a stuck or crashing async process.

Common Pitfalls

The most common mistake is using gdb for a normal application-level async bug that would have been easier to diagnose with logging or asyncio debug mode.

Another mistake is reading only the C backtrace and ignoring the Python-aware gdb helpers.

A third issue is forgetting that an async hang may really be a blocking call running on the event-loop thread.

Finally, if the Python binary lacks debug symbols or helper support, gdb becomes less informative, so the environment matters.

Summary

  • Use gdb for crashes, deadlocks, native hangs, and C-extension problems in async Python processes.
  • Start with gdb --args python app.py or attach with gdb -p <pid>.
  • Use bt for native frames and py-bt for Python frames when available.
  • For hangs, inspect all threads with thread apply all bt.
  • Pair gdb with asyncio debug mode and Python-level diagnostics.
  • 'gdb is most useful when the problem is below ordinary coroutine logic, not when the bug is just a missing await.'

Course illustration
Course illustration

All Rights Reserved.