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 前,先检查未关闭的响应、并发任务和连接上限。合理的并发负载可能需要更大的连接池,但扩大连接池不会修好一直不关闭的响应,只是给应用更多容量去占用。

在整个工作负载中保留并复用客户端。为了让每次请求表面上成功而反复新建客户端,可能掩盖生命周期问题,也会丢掉连接复用。

相关文章