PoolTimeout means HTTPX could not obtain a connection from its pool within the configured waiting time. It is not evidence that a new TCP connection to the proxy timed out. The second request might still be waiting inside your process.

This becomes easy to reproduce when a client permits one connection and the first streaming response is left open. Starting another request does not magically release that connection.

Keep streaming response ownership visible

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)

Replace the documentation proxy address before running it. The next request begins after the stream context exits. That exit closes the response even if code breaks out of the loop or raises an exception.

With manual streaming, such as client.send(request, stream=True), your code owns the eventual close. For an AsyncClient, the corresponding manual response cleanup is awaited with response.aclose(). Closing an unread response frees capacity but may require discarding its underlying connection rather than reusing it.

Read the exception name

ConnectTimeout concerns connection establishment. ReadTimeout concerns waiting for response data. PoolTimeout concerns getting a connection slot. Logging just “proxy timeout” loses the distinction needed to choose a fix.

Check open responses, concurrent tasks, and connection limits before swapping endpoint IPs. A larger pool can be appropriate for a legitimate concurrent workload, but it does not correct responses that are never closed. It merely gives the application more capacity to occupy.

Keep the client reusable across the workload. Rebuilding a client to make each request appear to work can conceal a lifecycle problem and discard connection reuse.

འབྲེལ་བའི་རྩོམ་ཡིག