[{"data":1,"prerenderedAt":1152},["ShallowReactive",2],{"page-\u002Fsystematic-debugging-performance-profiling\u002Fdebugging-async-code-and-event-loops\u002F":3},{"id":4,"title":5,"body":6,"description":1113,"extension":1114,"meta":1115,"navigation":360,"path":1148,"seo":1149,"stem":1150,"__hash__":1151},"content\u002Fsystematic-debugging-performance-profiling\u002Fdebugging-async-code-and-event-loops\u002Findex.md","Debugging Async Code and Event Loops",{"type":7,"value":8,"toc":1098},"minimark",[9,30,35,95,99,137,157,320,324,329,339,391,400,404,407,472,483,487,500,565,577,581,596,667,674,702,706,721,763,783,787,843,847,984,988,1007,1020,1041,1050,1054,1087,1094],[10,11,12,13,17,18,21,22,25,26,29],"p",{},"Async bugs rarely raise a clean traceback at the point of failure. A coroutine silently never runs, a blocking call freezes the whole loop, a task dies with its exception swallowed, or teardown trips ",[14,15,16],"code",{},"RuntimeError: Event loop is closed"," long after the real mistake. The reason is structural: in ",[14,19,20],{},"asyncio",", scheduling is decoupled from execution, exceptions live on ",[14,23,24],{},"Task"," objects until someone retrieves them, and one event loop multiplexes everything. This guide turns those invisible failures into observable ones — debug mode, slow-callback detection, never-awaited warnings, task introspection, and stepping through coroutines with ",[14,27,28],{},"pdb",".",[31,32,34],"h2",{"id":33},"prerequisites","Prerequisites",[36,37,38,61,75,89],"ul",{},[39,40,41,42,45,46,49,50,49,53,56,57,60],"li",{},"Python ",[14,43,44],{},"3.8+"," (",[14,47,48],{},"asyncio.all_tasks",", ",[14,51,52],{},"asyncio.current_task",[14,54,55],{},"asyncio.run"," are the modern, loop-agnostic APIs; the pre-3.7 ",[14,58,59],{},"get_event_loop"," patterns are deprecated).",[39,62,41,63,66,67,70,71,74],{},[14,64,65],{},"3.11+"," for the async-aware REPL (",[14,68,69],{},"python -m asyncio",") that lets you ",[14,72,73],{},"await"," at the prompt.",[39,76,77,80,81,84,85,88],{},[14,78,79],{},"aiomonitor >= 0.7"," for attaching to a live loop over a console (",[14,82,83],{},"pip install aiomonitor","); ",[14,86,87],{},"aiodebug"," for slow-callback logging hooks.",[39,90,91,94],{},[14,92,93],{},"tracemalloc"," (stdlib) for allocation tracebacks on never-awaited coroutines.",[31,96,98],{"id":97},"core-concept","Core concept",[10,100,101,102,104,105,49,108,49,111,114,115,118,119,122,123,126,127,131,132,136],{},"The event loop runs one callback at a time. A coroutine becomes a ",[14,103,24],{}," only when it is awaited or scheduled (",[14,106,107],{},"create_task",[14,109,110],{},"gather",[14,112,113],{},"ensure_future","); a bare ",[14,116,117],{},"some_coro()"," call just builds a coroutine object that does nothing until awaited — forget the await and it is garbage-collected unrun, producing the \"coroutine was never awaited\" warning. Because the loop is single-threaded, any synchronous blocking call (a ",[14,120,121],{},"requests"," GET, ",[14,124,125],{},"time.sleep",", a CPU-bound loop) freezes ",[128,129,130],"em",{},"every"," task, which ",[133,134,135],"strong",{},"debug mode"," surfaces as a slow-callback warning. Exceptions raised inside a task do not propagate to the caller; they are stored on the task and only re-raised when its result is awaited, which is why a crashed background task can vanish without a trace.",[10,138,139,141,142,49,145,148,149,152,153,156],{},[14,140,20],{}," debug mode is the single most valuable switch. Enable it via ",[14,143,144],{},"PYTHONASYNCIODEBUG=1",[14,146,147],{},"asyncio.run(main(), debug=True)",", or ",[14,150,151],{},"loop.set_debug(True)",". It logs callbacks slower than ",[14,154,155],{},"loop.slow_callback_duration"," (default 0.1s), warns on never-awaited coroutines with their origin, and checks that loop calls happen on the owning thread.",[158,159,162,316],"figure",{"className":160},[161],"diagram",[163,164,171,172,171,176,171,180,171,190,171,200,171,206,171,211,171,216,171,219,171,222,171,226,171,230,171,233,171,239,171,243,171,247,171,250,171,253,171,257,171,261,171,264,171,268,171,272,171,276,171,279,171,283,171,286,171,289,171,292,171,295,171,299,171,302,171,307,171,311],"svg",{"viewBox":165,"role":166,"ariaLabelledBy":167,"xmlns":170},"0 0 800 380","img",[168,169],"asyncdbg-t","asyncdbg-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[173,174,175],"title",{"id":168},"Task lifecycle on the event loop",[177,178,179],"desc",{"id":169},"A coroutine becomes a scheduled task, runs and suspends on awaits, then finishes or stores an exception; forgetting to await leaves it unrun.",[181,182,189],"text",{"x":183,"y":184,"textAnchor":185,"fontSize":186,"fontWeight":187,"fill":188},"400","32","middle","19","700","#3d405b","Where async tasks go wrong",[191,192],"rect",{"x":193,"y":194,"width":195,"height":196,"rx":197,"fill":198,"stroke":188,"strokeWidth":199},"40","62","200","64","12","#fffdf8","1.5",[181,201,205],{"x":202,"y":203,"textAnchor":185,"fontSize":204,"fontWeight":187,"fill":188},"140","90","13","coroutine object",[181,207,210],{"x":202,"y":208,"textAnchor":185,"fontSize":209,"fill":188},"110","11","created by some_coro()",[191,212],{"x":213,"y":194,"width":195,"height":196,"rx":197,"fill":198,"stroke":214,"strokeWidth":215},"300","#81b29a","2",[181,217,218],{"x":183,"y":203,"textAnchor":185,"fontSize":204,"fontWeight":187,"fill":188},"scheduled Task",[181,220,221],{"x":183,"y":208,"textAnchor":185,"fontSize":209,"fill":188},"await \u002F create_task",[191,223],{"x":224,"y":194,"width":195,"height":196,"rx":197,"fill":198,"stroke":225,"strokeWidth":215},"560","#f2cc8f",[181,227,229],{"x":228,"y":203,"textAnchor":185,"fontSize":204,"fontWeight":187,"fill":188},"660","running on loop",[181,231,232],{"x":228,"y":208,"textAnchor":185,"fontSize":209,"fill":188},"one task at a time",[234,235],"line",{"x1":236,"y1":237,"x2":238,"y2":237,"stroke":188,"strokeWidth":215},"240","94","298",[240,241],"polygon",{"points":242,"fill":188},"298,94 288,89 288,99",[234,244],{"x1":245,"y1":237,"x2":246,"y2":237,"stroke":188,"strokeWidth":215},"500","558",[240,248],{"points":249,"fill":188},"558,94 548,89 548,99",[191,251],{"x":224,"y":252,"width":195,"height":196,"rx":197,"fill":198,"stroke":214,"strokeWidth":215},"180",[181,254,256],{"x":228,"y":255,"textAnchor":185,"fontSize":204,"fontWeight":187,"fill":188},"208","done: result",[181,258,260],{"x":228,"y":259,"textAnchor":185,"fontSize":209,"fill":188},"228","awaited and returned",[191,262],{"x":213,"y":252,"width":195,"height":196,"rx":197,"fill":198,"stroke":263,"strokeWidth":215},"#e07a5f",[181,265,267],{"x":183,"y":266,"textAnchor":185,"fontSize":204,"fontWeight":187,"fill":188},"204","exception stored",[181,269,271],{"x":183,"y":270,"textAnchor":185,"fontSize":209,"fill":188},"224","silent until awaited",[234,273],{"x1":228,"y1":274,"x2":228,"y2":275,"stroke":214,"strokeWidth":215},"126","178",[240,277],{"points":278,"fill":214},"660,178 655,168 665,168",[234,280],{"x1":281,"y1":274,"x2":282,"y2":275,"stroke":263,"strokeWidth":215},"600","440",[240,284],{"points":285,"fill":263},"440,178 451,177 446,168",[191,287],{"x":193,"y":252,"width":195,"height":196,"rx":197,"fill":288,"stroke":263,"strokeWidth":215},"#f4f1de",[181,290,291],{"x":202,"y":266,"textAnchor":185,"fontSize":204,"fontWeight":187,"fill":188},"never awaited",[181,293,294],{"x":202,"y":270,"textAnchor":185,"fontSize":209,"fill":188},"GC'd, never ran",[234,296],{"x1":202,"y1":274,"x2":202,"y2":275,"stroke":263,"strokeWidth":215,"strokeDashArray":297},[298,298],"4",[240,300],{"points":301,"fill":263},"140,178 135,168 145,168",[191,303],{"x":193,"y":304,"width":305,"height":306,"rx":197,"fill":288,"stroke":188,"strokeWidth":199},"296","720","58",[181,308,310],{"x":183,"y":309,"textAnchor":185,"fontSize":204,"fontWeight":187,"fill":188},"322","Debug mode flags the red paths",[181,312,315],{"x":183,"y":313,"textAnchor":185,"fontSize":314,"fill":188},"342","11.5","unawaited coroutines, slow callbacks, lost errors",[317,318,319],"figcaption",{},"A coroutine only runs once awaited or scheduled as a task; an exception sits on the task until its result is retrieved, and a forgotten await leaves the coroutine garbage-collected and unrun. Debug mode makes these red paths visible.",[31,321,323],{"id":322},"step-by-step-implementation","Step-by-step implementation",[325,326,328],"h3",{"id":327},"_1-enable-debug-mode","1. Enable debug mode",[10,330,331,332,335,336,338],{},"The cleanest switch is the ",[14,333,334],{},"debug"," flag on ",[14,337,55],{},", which sets the loop into debug mode for the whole run:",[340,341,346],"pre",{"className":342,"code":343,"language":344,"meta":345,"style":345},"language-python shiki shiki-themes github-light github-dark","import asyncio\n\nasync def main() -> None:\n    await asyncio.sleep(0.01)\n\n# debug=True: log slow callbacks, warn on unawaited coroutines, check thread safety.\nasyncio.run(main(), debug=True)\n","python","",[14,347,348,355,362,368,374,379,385],{"__ignoreMap":345},[349,350,352],"span",{"class":234,"line":351},1,[349,353,354],{},"import asyncio\n",[349,356,358],{"class":234,"line":357},2,[349,359,361],{"emptyLinePlaceholder":360},true,"\n",[349,363,365],{"class":234,"line":364},3,[349,366,367],{},"async def main() -> None:\n",[349,369,371],{"class":234,"line":370},4,[349,372,373],{},"    await asyncio.sleep(0.01)\n",[349,375,377],{"class":234,"line":376},5,[349,378,361],{"emptyLinePlaceholder":360},[349,380,382],{"class":234,"line":381},6,[349,383,384],{},"# debug=True: log slow callbacks, warn on unawaited coroutines, check thread safety.\n",[349,386,388],{"class":234,"line":387},7,[349,389,390],{},"asyncio.run(main(), debug=True)\n",[10,392,393,394,396,397,399],{},"For a loop you manage yourself, call ",[14,395,151],{},"; to enable it globally without code changes, export ",[14,398,144],{}," before launching.",[325,401,403],{"id":402},"_2-catch-slow-callbacks-blocked-loop","2. Catch slow callbacks (blocked loop)",[10,405,406],{},"Any synchronous blocking call stalls the loop. Debug mode logs it:",[340,408,410],{"className":342,"code":409,"language":344,"meta":345,"style":345},"import asyncio, time\n\nasync def handler() -> None:\n    time.sleep(0.5)            # BUG: synchronous sleep blocks the whole loop\n\nasync def main() -> None:\n    loop = asyncio.get_running_loop()\n    loop.slow_callback_duration = 0.05    # lower threshold to catch shorter stalls\n    await handler()\n\nasyncio.run(main(), debug=True)\n# WARNING:asyncio:Executing \u003CHandle ...> took 0.500 seconds\n",[14,411,412,417,421,426,431,435,439,444,450,456,461,466],{"__ignoreMap":345},[349,413,414],{"class":234,"line":351},[349,415,416],{},"import asyncio, time\n",[349,418,419],{"class":234,"line":357},[349,420,361],{"emptyLinePlaceholder":360},[349,422,423],{"class":234,"line":364},[349,424,425],{},"async def handler() -> None:\n",[349,427,428],{"class":234,"line":370},[349,429,430],{},"    time.sleep(0.5)            # BUG: synchronous sleep blocks the whole loop\n",[349,432,433],{"class":234,"line":376},[349,434,361],{"emptyLinePlaceholder":360},[349,436,437],{"class":234,"line":381},[349,438,367],{},[349,440,441],{"class":234,"line":387},[349,442,443],{},"    loop = asyncio.get_running_loop()\n",[349,445,447],{"class":234,"line":446},8,[349,448,449],{},"    loop.slow_callback_duration = 0.05    # lower threshold to catch shorter stalls\n",[349,451,453],{"class":234,"line":452},9,[349,454,455],{},"    await handler()\n",[349,457,459],{"class":234,"line":458},10,[349,460,361],{"emptyLinePlaceholder":360},[349,462,464],{"class":234,"line":463},11,[349,465,390],{},[349,467,469],{"class":234,"line":468},12,[349,470,471],{},"# WARNING:asyncio:Executing \u003CHandle ...> took 0.500 seconds\n",[10,473,474,475,478,479,482],{},"The fix is to move blocking work off the loop with ",[14,476,477],{},"await loop.run_in_executor(None, blocking_fn)"," or an async-native client. ",[14,480,481],{},"aiodebug.log_slow_callbacks"," can route these warnings into structured logging in production.",[325,484,486],{"id":485},"_3-find-never-awaited-coroutines","3. Find never-awaited coroutines",[10,488,489,490,492,493,496,497,499],{},"A forgotten ",[14,491,73],{}," produces a ",[14,494,495],{},"RuntimeWarning"," at garbage-collection time — far from the bug. Promote it to an error and enable ",[14,498,93],{}," so the warning carries the allocation traceback:",[340,501,503],{"className":342,"code":502,"language":344,"meta":345,"style":345},"import asyncio, tracemalloc, warnings\n\ntracemalloc.start()                                   # capture where coroutines are created\nwarnings.simplefilter(\"error\", RuntimeWarning)        # turn the warning into a raised error\n\nasync def fetch() -> int:\n    return 42\n\nasync def main() -> None:\n    fetch()                  # BUG: missing await -> coroutine never runs\n    await asyncio.sleep(0)\n\nasyncio.run(main(), debug=True)\n",[14,504,505,510,514,519,524,528,533,538,542,546,551,556,560],{"__ignoreMap":345},[349,506,507],{"class":234,"line":351},[349,508,509],{},"import asyncio, tracemalloc, warnings\n",[349,511,512],{"class":234,"line":357},[349,513,361],{"emptyLinePlaceholder":360},[349,515,516],{"class":234,"line":364},[349,517,518],{},"tracemalloc.start()                                   # capture where coroutines are created\n",[349,520,521],{"class":234,"line":370},[349,522,523],{},"warnings.simplefilter(\"error\", RuntimeWarning)        # turn the warning into a raised error\n",[349,525,526],{"class":234,"line":376},[349,527,361],{"emptyLinePlaceholder":360},[349,529,530],{"class":234,"line":381},[349,531,532],{},"async def fetch() -> int:\n",[349,534,535],{"class":234,"line":387},[349,536,537],{},"    return 42\n",[349,539,540],{"class":234,"line":446},[349,541,361],{"emptyLinePlaceholder":360},[349,543,544],{"class":234,"line":452},[349,545,367],{},[349,547,548],{"class":234,"line":458},[349,549,550],{},"    fetch()                  # BUG: missing await -> coroutine never runs\n",[349,552,553],{"class":234,"line":463},[349,554,555],{},"    await asyncio.sleep(0)\n",[349,557,558],{"class":234,"line":468},[349,559,361],{"emptyLinePlaceholder":360},[349,561,563],{"class":234,"line":562},13,[349,564,390],{},[10,566,567,568,571,572,29],{},"Run the test suite with ",[14,569,570],{},"python -W error::RuntimeWarning -X tracemalloc"," to fail CI on the warning with a pinpoint traceback. This technique gets a full treatment in ",[573,574,576],"a",{"href":575},"\u002Fsystematic-debugging-performance-profiling\u002Fdebugging-async-code-and-event-loops\u002Ftracing-unawaited-coroutine-warnings\u002F","tracing \"coroutine was never awaited\" warnings",[325,578,580],{"id":579},"_4-introspect-running-tasks","4. Introspect running tasks",[10,582,583,584,587,588,591,592,595],{},"When the loop hangs, ask it what is pending. ",[14,585,586],{},"asyncio.all_tasks()"," returns every live task; ",[14,589,590],{},"get_coro()"," and ",[14,593,594],{},"get_stack()"," show what each one is and where it is suspended:",[340,597,599],{"className":342,"code":598,"language":344,"meta":345,"style":345},"import asyncio\n\nasync def slow() -> None:\n    await asyncio.sleep(3600)\n\nasync def main() -> None:\n    asyncio.create_task(slow(), name=\"slow-worker\")\n    await asyncio.sleep(0)                      # let the task start and suspend\n    for task in asyncio.all_tasks():\n        print(task.get_name(), task.get_coro().__qualname__)\n        task.print_stack()                      # where the task is parked\n    print(\"current:\", asyncio.current_task().get_name())\n\nasyncio.run(main())\n",[14,600,601,605,609,614,619,623,627,632,637,642,647,652,657,661],{"__ignoreMap":345},[349,602,603],{"class":234,"line":351},[349,604,354],{},[349,606,607],{"class":234,"line":357},[349,608,361],{"emptyLinePlaceholder":360},[349,610,611],{"class":234,"line":364},[349,612,613],{},"async def slow() -> None:\n",[349,615,616],{"class":234,"line":370},[349,617,618],{},"    await asyncio.sleep(3600)\n",[349,620,621],{"class":234,"line":376},[349,622,361],{"emptyLinePlaceholder":360},[349,624,625],{"class":234,"line":381},[349,626,367],{},[349,628,629],{"class":234,"line":387},[349,630,631],{},"    asyncio.create_task(slow(), name=\"slow-worker\")\n",[349,633,634],{"class":234,"line":446},[349,635,636],{},"    await asyncio.sleep(0)                      # let the task start and suspend\n",[349,638,639],{"class":234,"line":452},[349,640,641],{},"    for task in asyncio.all_tasks():\n",[349,643,644],{"class":234,"line":458},[349,645,646],{},"        print(task.get_name(), task.get_coro().__qualname__)\n",[349,648,649],{"class":234,"line":463},[349,650,651],{},"        task.print_stack()                      # where the task is parked\n",[349,653,654],{"class":234,"line":468},[349,655,656],{},"    print(\"current:\", asyncio.current_task().get_name())\n",[349,658,659],{"class":234,"line":562},[349,660,361],{"emptyLinePlaceholder":360},[349,662,664],{"class":234,"line":663},14,[349,665,666],{},"asyncio.run(main())\n",[10,668,669,670,673],{},"For a live, long-running service, attach ",[14,671,672],{},"aiomonitor"," and inspect tasks over a console without stopping the process:",[340,675,677],{"className":342,"code":676,"language":344,"meta":345,"style":345},"import aiomonitor, asyncio\n\nasync def main() -> None:\n    with aiomonitor.start_monitor(asyncio.get_running_loop()):\n        await asyncio.sleep(3600)               # `telnet localhost 50101`, then `ps`\u002F`where`\n",[14,678,679,684,688,692,697],{"__ignoreMap":345},[349,680,681],{"class":234,"line":351},[349,682,683],{},"import aiomonitor, asyncio\n",[349,685,686],{"class":234,"line":357},[349,687,361],{"emptyLinePlaceholder":360},[349,689,690],{"class":234,"line":364},[349,691,367],{},[349,693,694],{"class":234,"line":370},[349,695,696],{},"    with aiomonitor.start_monitor(asyncio.get_running_loop()):\n",[349,698,699],{"class":234,"line":376},[349,700,701],{},"        await asyncio.sleep(3600)               # `telnet localhost 50101`, then `ps`\u002F`where`\n",[325,703,705],{"id":704},"_5-step-through-with-pdb","5. Step through with pdb",[10,707,708,711,712,714,715,717,718,720],{},[14,709,710],{},"breakpoint()"," works inside a coroutine; the loop pauses while you are at the prompt (which can itself trip slow-callback warnings — expected). On Python ",[14,713,65],{},", the async REPL (",[14,716,69],{},") lets you ",[14,719,73],{}," expressions at the prompt to inspect coroutine results interactively:",[340,722,724],{"className":342,"code":723,"language":344,"meta":345,"style":345},"import asyncio\n\nasync def compute(x: int) -> int:\n    result = x * 2\n    breakpoint()              # pdb here; `p result`, `await some_coro()` on 3.11+ REPL\n    return result\n\nasyncio.run(compute(21))\n",[14,725,726,730,734,739,744,749,754,758],{"__ignoreMap":345},[349,727,728],{"class":234,"line":351},[349,729,354],{},[349,731,732],{"class":234,"line":357},[349,733,361],{"emptyLinePlaceholder":360},[349,735,736],{"class":234,"line":364},[349,737,738],{},"async def compute(x: int) -> int:\n",[349,740,741],{"class":234,"line":370},[349,742,743],{},"    result = x * 2\n",[349,745,746],{"class":234,"line":376},[349,747,748],{},"    breakpoint()              # pdb here; `p result`, `await some_coro()` on 3.11+ REPL\n",[349,750,751],{"class":234,"line":381},[349,752,753],{},"    return result\n",[349,755,756],{"class":234,"line":387},[349,757,361],{"emptyLinePlaceholder":360},[349,759,760],{"class":234,"line":446},[349,761,762],{},"asyncio.run(compute(21))\n",[10,764,765,766,770,771,775,776,778,779,29],{},"When the breakpoint must live inside an async test, scope the loop correctly first — see ",[573,767,769],{"href":768},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Fhow-to-scope-pytest-fixtures-for-async-tests\u002F","how to scope pytest fixtures for async tests"," and the ",[573,772,774],{"href":773},"\u002Fadvanced-pytest-architecture-configuration\u002Fmastering-pytest-fixtures\u002Fpytest-asyncio-vs-anyio-scoping-trade-offs\u002F","pytest-asyncio vs anyio scoping trade-offs"," so the breakpoint runs on a live loop. General ",[14,777,28],{}," mechanics live in ",[573,780,782],{"href":781},"\u002Fsystematic-debugging-performance-profiling\u002Finteractive-debugging-with-pdb-and-ipdb\u002F","interactive debugging with pdb and ipdb",[31,784,786],{"id":785},"verification","Verification",[36,788,789,806,815,828],{},[39,790,791,794,795,798,799,802,803,29],{},[133,792,793],{},"Debug mode is on:"," ",[14,796,797],{},"print(asyncio.get_running_loop().get_debug())"," returns ",[14,800,801],{},"True"," inside ",[14,804,805],{},"main",[39,807,808,811,812,29],{},[133,809,810],{},"Slow callbacks are caught:"," the \"Executing ... took N seconds\" warning fires for a deliberately blocking call once you lower ",[14,813,814],{},"slow_callback_duration",[39,816,817,820,821,824,825,827],{},[133,818,819],{},"Unawaited coroutines fail loudly:"," running under ",[14,822,823],{},"-W error::RuntimeWarning"," turns a missing ",[14,826,73],{}," into a raised error rather than a late GC warning.",[39,829,830,833,834,836,837,839,840,842],{},[133,831,832],{},"No task leaks at shutdown:"," after ",[14,835,55],{}," returns, ",[14,838,586],{}," is empty (it raises outside a loop, so check inside a final ",[14,841,73],{},"). Pending tasks at teardown are the classic cause of the next error.",[31,844,846],{"id":845},"troubleshooting","Troubleshooting",[848,849,850,866],"table",{},[851,852,853],"thead",{},[854,855,856,860,863],"tr",{},[857,858,859],"th",{},"Symptom",[857,861,862],{},"Root cause",[857,864,865],{},"Fix",[867,868,869,895,912,929,946,959],"tbody",{},[854,870,871,877,880],{},[872,873,874],"td",{},[14,875,876],{},"RuntimeWarning: coroutine '...' was never awaited",[872,878,879],{},"A coroutine was created but never awaited or scheduled",[872,881,882,884,885,888,889,891,892,894],{},[14,883,73],{}," it, ",[14,886,887],{},"asyncio.create_task(...)",", or pass to ",[14,890,110],{},"; run with ",[14,893,93],{}," to find it",[854,896,897,902,905],{},[872,898,899],{},[14,900,901],{},"Executing \u003CHandle ...> took N seconds",[872,903,904],{},"Synchronous blocking call on the loop",[872,906,907,908,911],{},"Move work to ",[14,909,910],{},"run_in_executor"," or an async client",[854,913,914,917,920],{},[872,915,916],{},"Loop hangs with no error",[872,918,919],{},"A task is awaiting something that never resolves",[872,921,922,924,925,928],{},[14,923,586],{}," + ",[14,926,927],{},"task.print_stack()"," to find the parked task",[854,930,931,934,937],{},[872,932,933],{},"Background task crashed silently",[872,935,936],{},"Exception stored on the task, never retrieved",[872,938,939,940,942,943,945],{},"Add a done callback or ",[14,941,73],{},"\u002F",[14,944,110],{}," the task; debug mode surfaces it",[854,947,948,953,956],{},[872,949,950,952],{},[14,951,16],{}," at teardown",[872,954,955],{},"Reusing or scheduling on a closed loop",[872,957,958],{},"See the dedicated guide below",[854,960,961,966,975],{},[872,962,963],{},[14,964,965],{},"RuntimeError: no running event loop",[872,967,968,969,942,971,974],{},"Calling ",[14,970,107],{},[14,972,973],{},"get_running_loop"," outside a coroutine",[872,976,977,978,981,982],{},"Call inside an ",[14,979,980],{},"async def"," driven by ",[14,983,55],{},[31,985,987],{"id":986},"frequently-asked-questions","Frequently Asked Questions",[10,989,990,993,994,996,997,1000,1001,1003,1004,1006],{},[133,991,992],{},"How do I enable asyncio debug mode?","\nSet the environment variable ",[14,995,144],{},", pass ",[14,998,999],{},"debug=True"," to ",[14,1002,55],{},", or call ",[14,1005,151],{}," on a running loop. Debug mode logs slow callbacks, warns about coroutines that were never awaited, and checks that calls happen on the right thread.",[10,1008,1009,1012,1013,1016,1017,29],{},[133,1010,1011],{},"Why do I get a coroutine was never awaited warning?","\nYou called an async function but never awaited the coroutine it returned or scheduled it as a task, so it was garbage collected without running. Await it, wrap it in ",[14,1014,1015],{},"asyncio.create_task",", or pass it to ",[14,1018,1019],{},"asyncio.gather",[10,1021,1022,1025,1026,1029,1030,1033,1034,591,1037,1040],{},[133,1023,1024],{},"How do I see every task currently running on the event loop?","\nCall ",[14,1027,1028],{},"asyncio.all_tasks(loop)"," to get the set of pending tasks, and ",[14,1031,1032],{},"asyncio.current_task()"," for the one running now. Each task's ",[14,1035,1036],{},"get_coro",[14,1038,1039],{},"get_stack"," methods reveal what it is and where it is suspended.",[10,1042,1043,1046,1047,1049],{},[133,1044,1045],{},"Can I use pdb inside an async function?","\nYes. ",[14,1048,710],{}," works inside a coroutine on Python 3.7+, and on 3.11+ the asyncio REPL plus pdb handles awaits at the prompt. The loop is paused while you are at the breakpoint, so long pauses can trip slow-callback warnings.",[31,1051,1053],{"id":1052},"related-guides","Related guides",[36,1055,1056,1063,1068,1075,1080],{},[39,1057,1058,1059,29],{},"The most common async teardown failure has its own deep dive: ",[573,1060,1062],{"href":1061},"\u002Fsystematic-debugging-performance-profiling\u002Fdebugging-async-code-and-event-loops\u002Fdebugging-event-loop-is-closed-runtimeerror\u002F","debugging \"Event loop is closed\" RuntimeError",[39,1064,1065,1066,29],{},"Turn a vague late warning into a pinpoint traceback with ",[573,1067,576],{"href":575},[39,1069,1070,1071,29],{},"When the async bug is really a blocked loop burning CPU, profile it with ",[573,1072,1074],{"href":1073},"\u002Fsystematic-debugging-performance-profiling\u002Fcpu-profiling-with-cprofile-and-py-spy\u002F","cProfile and py-spy",[39,1076,1077,1078,29],{},"Get async fixtures and their loops right with ",[573,1079,769],{"href":768},[39,1081,1082,1083,29],{},"Track down coroutine leaks that surface as allocation growth with ",[573,1084,1086],{"href":1085},"\u002Fsystematic-debugging-performance-profiling\u002Fmemory-profiling-with-tracemalloc\u002F","memory profiling using tracemalloc",[10,1088,1089,1090],{},"← Back to ",[573,1091,1093],{"href":1092},"\u002Fsystematic-debugging-performance-profiling\u002F","Systematic Debugging & Performance Profiling",[1095,1096,1097],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":345,"searchDepth":357,"depth":357,"links":1099},[1100,1101,1102,1109,1110,1111,1112],{"id":33,"depth":357,"text":34},{"id":97,"depth":357,"text":98},{"id":322,"depth":357,"text":323,"children":1103},[1104,1105,1106,1107,1108],{"id":327,"depth":364,"text":328},{"id":402,"depth":364,"text":403},{"id":485,"depth":364,"text":486},{"id":579,"depth":364,"text":580},{"id":704,"depth":364,"text":705},{"id":785,"depth":357,"text":786},{"id":845,"depth":357,"text":846},{"id":986,"depth":357,"text":987},{"id":1052,"depth":357,"text":1053},"Debug asyncio with PYTHONASYNCIODEBUG and loop.set_debug, catch slow callbacks and never-awaited coroutines, introspect tasks with asyncio.all_tasks, and use pdb in async.","md",{"slug":1116,"type":1117,"breadcrumb":1118,"datePublished":1119,"dateModified":1119,"faq":1120,"howto":1129},"debugging-async-code-and-event-loops","cluster","Debugging Async Code","2026-06-18",[1121,1123,1125,1127],{"q":992,"a":1122},"Set the environment variable PYTHONASYNCIODEBUG=1, pass debug=True to asyncio.run, or call loop.set_debug(True) on a running loop. Debug mode logs slow callbacks, warns about coroutines that were never awaited, and checks that calls happen on the right thread.",{"q":1011,"a":1124},"You called an async function but never awaited the coroutine it returned or scheduled it as a task, so it was garbage collected without running. Await it, wrap it in asyncio.create_task, or pass it to asyncio.gather.",{"q":1024,"a":1126},"Call asyncio.all_tasks(loop) to get the set of pending tasks, and asyncio.current_task() for the one running now. Each task's get_coro and get_stack methods reveal what it is and where it is suspended.",{"q":1045,"a":1128},"Yes. breakpoint() works inside a coroutine on Python 3.7+, and on 3.11+ the asyncio REPL plus pdb handles awaits at the prompt. The loop is paused while you are at the breakpoint, so long pauses can trip slow-callback warnings.",{"name":1130,"description":1131,"steps":1132},"How to debug asyncio code and event loops","Turn on asyncio debug mode, surface slow callbacks and unawaited coroutines, introspect tasks, and step through with pdb.",[1133,1136,1139,1142,1145],{"name":1134,"text":1135},"Enable debug mode","Set PYTHONASYNCIODEBUG=1 or pass debug=True to asyncio.run, or call loop.set_debug(True), to log slow callbacks and unawaited coroutines.",{"name":1137,"text":1138},"Catch slow callbacks","Read the 'Executing took N seconds' warnings and tune the threshold with loop.slow_callback_duration to find blocking calls on the loop.",{"name":1140,"text":1141},"Find never-awaited coroutines","Run with warnings as errors and tracemalloc enabled so the warning carries the allocation traceback to the missing await.",{"name":1143,"text":1144},"Introspect running tasks","Call asyncio.all_tasks and asyncio.current_task and inspect each task with get_coro and get_stack to see what is pending and where.",{"name":1146,"text":1147},"Step through with pdb","Drop a breakpoint() inside the coroutine, or use aiomonitor to attach to a live loop, and inspect awaited values at the prompt.","\u002Fsystematic-debugging-performance-profiling\u002Fdebugging-async-code-and-event-loops",{"title":5,"description":1113},"systematic-debugging-performance-profiling\u002Fdebugging-async-code-and-event-loops\u002Findex","N69b_T3Ze_KhqqbEo2zrDD8h3J_meXn1OuGqi-UK-Io",1781793487407]