潜在问题: `AsyncClient.stream` 的双重取消可能会泄露活动连接
作者: BenjaminChoou创建于 2026年2月26日更新于 2026年9月16日
∮我想做的事∮
我有一个背景任务,需要“httpx.AsyncClient.stream(.)”的响应,我想提早停止,我就取消这项任务。 在一些真实的代码路径中,同一任务可能会被取消两次(例如连锁关闭).
∮我所看到的∮
如果我两次用等待Ayncio.sleep(0)'称呼ask.cancel()',在任务完成后,连接似乎仍然在基本池中“活动”,尽管我明确关闭了反应/显示器。
重复图案会长出池的"连接:N活"到"max connections",此时下一个请求块等待连接.
单取消不漏出: 池在每次迭代后返回为 0 连接 。
关键细节:复制要求 " 等待Ayncio.sleep(0) " 在两次`cancel() ' 通话之间;删除或更长时间地睡觉通常避免问题。
□ 最小复制
运行 :
{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}你觉得呢? 运行测试.py
要使吊定性,比起 " limits.max connections " (如 " max connections=10 " 时为12+)进行更多的迭代。
重写脚本:`test.py'
py
导入 Ayncio
导入 httpx
从日志导入 INFO 中获取 Logger, basicConfig
基本Config( 级别=INFO)
logger = 获取 Logger ( name )
限制 = httpx.Limits(最大 连接=10,最大 保持 连接=5)
客户端 = httpx.AsyncClient(
核实=虚假,
限制=限制,
超时=httpx.timeout (30.0), (中文(简体) ).
(中文(简体) ).
async def stream (): (中文(简体) ).
async with client.stream("GET","https://httpbin.org/stream/100")作为回应:
尝试 :
响应行的async.aiter 行(:
产量线
终于:
# 即使取消,也要确保接近运行。
等待 asyncio.shield(响应.aclose())
logger.info (“ 答复已完成 ” )
async def run impl task(名称,站站站站,站站站站站站牌:asyncio. 活动 :
梯度 = 流 ()
尝试 :
cnt=0 (单位:千美元)
显示器中 的同步 :
(单位:千美元)
如果停止 step和 cmt > stop step而不会停止 signal.is set ():
logger.info(f" [{name}] 发送停止信号")
停止( signal. set ()
终于:
{\fn方正粗倩简体\fs12\an8\1cHFFFF00\b0}确保电梯关闭
正在等待 asyncio.shield( terrator. aclose ()) 。
logger.info(f) [{name}] 转发器已关闭)
def log pool status( 相位: str) :
transport = getattr(客户端, “ Transport”, 无)
pool = gettr( 运输, “ pool”, 无) 如果运输有其他的话
logger.info ("Pool [%s]: %s",相位,池)
async def 模拟( 名: str, stop step: int) :
log pool status (f"{name} 在创建 task 之前)
stop signal = asyncio. (中文(简体) ). 活动( )
bg task = asyncio.create task ( run impl task (名称, stop step, stop signal)) 互联网档案馆的存檔,存档日期2013-12-11.
等待停止 signal.wait ()
log pool status(f"{name} 在取消前)
# 第1个取消
bg 任务.cancel ()
# 复制的关键。
等待 Ayncio.sleep( 0) 状态
# 第2回取消引发了泄密.
bg 任务.cancel ()
尝试 :
等待 bg 任务
除了阿辛西奥 已取消错误 :
. . . . . . .内容来源: encode/httpx