Memory leak on multiple async connections. #2785
-
To Reproduceimport asyncio
async def leak():
async with httpx.AsyncClient(timeout=21) as client:
async with client.stream("GET", "https://socketsbay.com/wss/v2/1/demo/") as response:
pass
input("before")
asyncio.run(asyncio.gather(*[leak() for _ in range(100)]))
input("after") Expected behaviorNo memory leak Configuration
Additional contextIn my run of above code memory bump from 10MB to 80MB |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 6 replies
-
@T-256 How are you measuring memory usage? I get connection timeouts with the example website you've provided. I've used e.g. import tracemalloc
import asyncio
import gc
import httpx
tracemalloc.start()
async def leak():
async with httpx.AsyncClient(timeout=21) as client:
async with client.stream("GET", "https://socketsbay.com/wss/v2/1/demo/") as response:
pass
async def main():
await asyncio.gather(*[leak() for _ in range(100)])
snapshot1 = tracemalloc.take_snapshot()
print("Prerun usage {}".format(tracemalloc.get_traced_memory()[0]))
try:
asyncio.run(main())
except Exception:
pass
print("Postrun usage {} (peak {})".format(*tracemalloc.get_traced_memory()))
gc.collect()
print("Postcollect usage {}".format(tracemalloc.get_traced_memory()[0]))
snapshot2 = tracemalloc.take_snapshot()
top_stats = snapshot2.compare_to(snapshot1, 'lineno')
print("[ Top 10 differences ]")
for stat in top_stats[:10]:
print(stat) |
Beta Was this translation helpful? Give feedback.
-
After debugging I found that |
Beta Was this translation helpful? Give feedback.
-
Don't do this... async def request():
# New client per request, no connection pooling.
async with httpx.AsyncClient(timeout=21) as client:
async with client.stream("GET", "https://socketsbay.com/wss/v2/1/demo/") as response:
pass
async def main():
await asyncio.gather(*[request() for _ in range(100)]) Do this... async def request(client):
# Single client shared between all requests.
async with client.stream("GET", "https://socketsbay.com/wss/v2/1/demo/") as response:
...
async def main():
async with httpx.AsyncClient(timeout=21) as client:
await asyncio.gather(*[request(client) for _ in range(100)]) |
Beta Was this translation helpful? Give feedback.
-
I'm running into a similar bug which might be a memory leak. This happens over a long period of time with lots of async requests. Below is the memray output: Still investigating. Upstream issue: blacklanternsecurity/bbot#1615. |
Beta Was this translation helpful? Give feedback.
This turned out to be our fault and not a bug in httpx. We were creating multiple httpx clients without closing them. 🤦