HTTPX 的 PoolTimeout 不是代理连接超时
识别 HTTPX 中等待连接池的超时,及时释放流式响应;不要急着替换一个可能根本没收到下一次请求的代理。
PoolTimeout 表示 HTTPX 未能在配置的等待时间内从连接池取得连接。它不能证明到代理的新 TCP 连接超时。第二个请求可能仍在你的进程内部排队。
当客户端只允许一条连接,而第一个流式响应一直没关时,这个情况很容易重现。启动另一个请求并不会自动释放那条连接。
明确谁负责关闭流式响应
import httpx
limits = httpx.Limits(
max_connections=1,
max_keepalive_connections=1,
)
timeout = httpx.Timeout(
connect=5, read=10, write=10, pool=1
)
with httpx.Client(
proxy="http://203.0.113.10:8080",
trust_env=False,
limits=limits,
timeout=timeout,
) as client:
with client.stream("GET", "https://example.com/") as response:
print(response.status_code)
for chunk in response.iter_bytes():
print(len(chunk))
next_response = client.get("https://example.com/")
print(next_response.status_code)
运行前先替换文档中的代理示例地址。下一次请求在流式上下文退出后才开始。即使代码提前跳出循环或抛出异常,退出上下文也会关闭响应。
手动处理流式响应时,例如 client.send(request, stream=True),最终关闭响应的责任在你的代码。对于 AsyncClient,对应的手动清理需要等待 response.aclose()。关闭尚未读完的响应会释放容量,但可能需要丢弃底层连接,无法继续复用它。
看清异常名称
ConnectTimeout 涉及建立连接,ReadTimeout 涉及等待响应数据,PoolTimeout 涉及取得连接名额。日志里只记一句“代理超时”,会丢掉选择修复方法所需的区别。
更换端点 IP 前,先检查未关闭的响应、并发任务和连接上限。合理的并发负载可能需要更大的连接池,但扩大连接池不会修好一直不关闭的响应,只是给应用更多容量去占用。
在整个工作负载中保留并复用客户端。为了让每次请求表面上成功而反复新建客户端,可能掩盖生命周期问题,也会丢掉连接复用。