我相信在使用 aiohttp ClientSession 请求时,我在长期存在的应用程序中发现了内存泄漏。如果按顺序等待每个发出请求的协程,那么一切似乎都很好。然而,并发运行时似乎存在请求上下文管理器对象的泄漏。
请考虑以下示例代码:
import logging
import tracemalloc
import asyncio
import aiohttp
async def log_allocations_coro():
while True:
await asyncio.sleep(120)
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
str_list = [str(x) for x in top_stats[:5]]
logging.info("\n".join(str_list))
async def do_request():
try:
async with session.request("GET", "http://192.168.1.1") as response:
text = await response.text()
except:
logging.exception("Request failed")
async def main():
tracemalloc.start()
asyncio.ensure_future(log_allocations_coro())
timeout = aiohttp.ClientTimeout(total=1)
global session
session = aiohttp.ClientSession(timeout=timeout)
while True:
tasks = [ do_request(), do_request() ]
await asyncio.gather(*tasks)
await asyncio.sleep(2)
if __name__ == '__main__':
logging.basicConfig(format='%(asctime)s %(message)s', level=logging.INFO)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
tracemalloc
协程每两分钟记录一次内存分配。这显示了 aiohttp/client.py
中的分配计数,其中 request()
返回 _RequestContextManager
随着时间的推移而增加,最初很快,但随后减慢,直到达到峰值,然后似乎相当稳定。
但是, 随后观察到,如果出现网络问题并且请求开始失败,则计数会再次上升 - 并且在问题解决后不会再下降。
这是泄漏吗?如果是这样,有办法解决吗?
感谢您的阅读!
正如我在上面的评论中提到的,
while True
给我带来了协程泄漏的问题。考虑将循环重构为如下所示:
async def main():
tracemalloc.start()
asyncio.ensure_future(log_allocations_coro())
timeout = aiohttp.ClientTimeout(total=1)
global session
session = aiohttp.ClientSession(timeout=timeout)
await forever_do_request()
async def forever_do_request(loop)
tasks = [ do_request(), do_request() ]
await asyncio.gather(*tasks)
await asyncio.get_event_loop().call_later(2,forever_do_request)
以这种方式,当forever_do_request被
call_later
d时,它的未来在tasks
中超出范围,并且可以被垃圾收集/从事件循环中删除,这与用于延迟的asyncio.sleep
具有类似的效果执行循环代码块。