Jump to solution
Verify

The Fix

Addresses a potential memory leak by breaking cyclic references when there is an exception handling a request.

Based on closed aio-libs/aiohttp issue #10548 · PR/commit linked

Jump to Verify Open PR/Commit
@@ -17,3 +17,6 @@ indent_style = tab [*.{yml,yaml}] indent_size = 2 + +[*.rst] +max_line_length = 80
repro.py
from aiohttp import web import gc import asyncio import tracemalloc from time import time import objgraph gc.set_debug(gc.DEBUG_LEAK) def get_garbage(): result = [] gc.collect() for obj in gc.garbage: obj_name = type(obj).__name__ result.append(f'{obj_name}') if obj_name in ('Request',): print('Request not collected!') objgraph.show_backrefs( obj, max_depth=30, too_many=50, filename=f"/tmp/{int(time() * 1000)}err_referrers.png", ) return result async def hanlder(request): print(f'read request') req = await request.json() return web.Response(text="Request has been receieved") async def on_startup(app) -> None: # asyncio.create_task(show_memory()) asyncio.create_task(show_objgraph()) async def show_objgraph(): while True: await asyncio.sleep(10) gc.collect() print(f'Garbage objects: {get_garbage()}') async def show_memory(): print('start tracing memory') tracemalloc.start(25) start = tracemalloc.take_snapshot() snapshot_num = 1 while True: await asyncio.sleep(20) current = tracemalloc.take_snapshot() # compare current snapshot to starting snapshot stats = current.compare_to(start, 'filename') print('top diffs since start') # print top diffs: current snapshot - start snapshot for i, stat in enumerate(stats[:15], 1): print(f'top diffs: {i}, {str(stat)}') traces = current.statistics('traceback') for stat in traces[:2]: for line in stat.traceback.format(): print(line) snapshot_num = snapshot_num + 1 my_web_app = web.Application() my_web_app.router.add_route('POST', '/image', hanlder) my_web_app.on_startup.append(on_startup) web.run_app(my_web_app)
verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
fix.md
Option A — Apply the official fix\nAddresses a potential memory leak by breaking cyclic references when there is an exception handling a request.\nWhen NOT to use: This fix should not be applied if the application relies on the cyclic references for functionality.\n\n

Why This Fix Works in Production

  • Trigger: Memory Leak in web request due to cyclic reference
  • Mechanism: Cyclic references in the Request object prevent garbage collection, leading to increased memory usage
Production impact:
  • If left unfixed, this can cause silent data inconsistencies that propagate (bad cache entries, incorrect downstream decisions).

Why This Breaks in Prod

  • Cyclic references in the Request object prevent garbage collection, leading to increased memory usage
  • Production symptom (often without a traceback): Memory Leak in web request due to cyclic reference

Proof / Evidence

Discussion

High-signal excerpts from the issue thread (symptoms, repros, edge-cases).

“looks like the other one I found isn't actually a problem it does become unreachable get cleaned up. closing this via https://github.com/aio-libs/aiohttp/pull/10571”
@bdraco · 2025-03-16 · confirmation · source
“While I can confirm reproduction, I have had no luck figuring out how to fix this as it seems its referenced in more than one…”
@bdraco · 2025-03-16 · repro detail · source
“Can you reproduce this on 3.11.x? We aren't shipping new builds of 3.10 anymore.”
@bdraco · 2025-03-14 · source
“Doesn't seem to be reproducible on master”
@bdraco · 2025-03-16 · source

Failure Signature (Search String)

  • Memory Leak in web request due to cyclic reference
Copy-friendly signature
signature.txt
Failure Signature ----------------- Memory Leak in web request due to cyclic reference import tracemalloc

Error Message

Signature-only (no traceback captured)
error.txt
Error Message ------------- Memory Leak in web request due to cyclic reference import tracemalloc

Minimal Reproduction

repro.py
from aiohttp import web import gc import asyncio import tracemalloc from time import time import objgraph gc.set_debug(gc.DEBUG_LEAK) def get_garbage(): result = [] gc.collect() for obj in gc.garbage: obj_name = type(obj).__name__ result.append(f'{obj_name}') if obj_name in ('Request',): print('Request not collected!') objgraph.show_backrefs( obj, max_depth=30, too_many=50, filename=f"/tmp/{int(time() * 1000)}err_referrers.png", ) return result async def hanlder(request): print(f'read request') req = await request.json() return web.Response(text="Request has been receieved") async def on_startup(app) -> None: # asyncio.create_task(show_memory()) asyncio.create_task(show_objgraph()) async def show_objgraph(): while True: await asyncio.sleep(10) gc.collect() print(f'Garbage objects: {get_garbage()}') async def show_memory(): print('start tracing memory') tracemalloc.start(25) start = tracemalloc.take_snapshot() snapshot_num = 1 while True: await asyncio.sleep(20) current = tracemalloc.take_snapshot() # compare current snapshot to starting snapshot stats = current.compare_to(start, 'filename') print('top diffs since start') # print top diffs: current snapshot - start snapshot for i, stat in enumerate(stats[:15], 1): print(f'top diffs: {i}, {str(stat)}') traces = current.statistics('traceback') for stat in traces[:2]: for line in stat.traceback.format(): print(line) snapshot_num = snapshot_num + 1 my_web_app = web.Application() my_web_app.router.add_route('POST', '/image', hanlder) my_web_app.on_startup.append(on_startup) web.run_app(my_web_app)

What Broke

Memory usage grows significantly during web requests, causing potential service degradation.

Why It Broke

Cyclic references in the Request object prevent garbage collection, leading to increased memory usage

Fix Options (Details)

Option A — Apply the official fix

Addresses a potential memory leak by breaking cyclic references when there is an exception handling a request.

When NOT to use: This fix should not be applied if the application relies on the cyclic references for functionality.

Fix reference: https://github.com/aio-libs/aiohttp/pull/3462

Last verified: 2026-02-09. Validate in your environment.

Get updates

We publish verified fixes weekly. No spam.

Subscribe

When NOT to Use This Fix

  • This fix should not be applied if the application relies on the cyclic references for functionality.

Verify Fix

verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.

Did This Fix Work in Your Case?

Quick signal helps us prioritize which fixes to verify and improve.

Prevention

  • Track RSS + object counts after deployments; alert on monotonic growth and GC pressure.
  • Add a long-running test that repeats the failing call path and asserts stable memory.

Related Issues

No related fixes found.

Sources

We don’t republish the full GitHub discussion text. Use the links above for context.