Fine-grained timeouts, pool limits and retries

HTTPX · Advanced · 11 min

Under load, the pool and timeout settings decide whether a service degrades gracefully or stalls.

httpx.Timeout has four parts:

  • connect: establishing the connection,
  • read: waiting for a chunk of data from the server,
  • write: sending a chunk of the request body,
  • pool: waiting to acquire a connection from the pool.

httpx.Timeout(10.0, connect=2.0) sets 10 seconds everywhere except connect. You can pass a Timeout to a client and override it per request. As with most clients, read is per chunk, not a total deadline; for a total limit wrap the call with asyncio.timeout() in async code.

httpx.Limits controls the pool:

  • max_connections (default 100): total concurrent connections,
  • max_keepalive_connections (default 20): idle connections kept for reuse,
  • keepalive_expiry (default 5 seconds): how long idle connections are kept.

When all connections are busy, requests wait for one and raise httpx.PoolTimeout if the pool timeout expires. A PoolTimeout under load means your concurrency exceeds max_connections; either raise the limit or reduce concurrency.

HTTPX has no built-in retry for HTTP statuses. httpx.HTTPTransport(retries=3) (and AsyncHTTPTransport) only retries connection failures (ConnectError, ConnectTimeout), which are always safe. For retrying 503s or 429s, write a small loop, a custom transport, or use a library such as tenacity or stamina.

Example

import httpx

timeout = httpx.Timeout(10.0, connect=2.0, pool=1.0)
print(timeout)   # Timeout(connect=2.0, read=10.0, write=10.0, pool=1.0)

limits = httpx.Limits(max_connections=1, max_keepalive_connections=1)
transport = httpx.HTTPTransport(retries=2, limits=limits)  # connect retries only

with httpx.Client(transport=transport, timeout=timeout,
                  base_url="https://httpbin.org") as client:
    with client.stream("GET", "/bytes/10"):           # holds the only connection
        try:
            client.get("/get")                         # must wait for the pool
        except httpx.PoolTimeout:
            print("pool exhausted")                    # pool exhausted
    print(client.get("/get").status_code)              # 200 (connection free again)

Remember

  • Timeout has connect, read, write and pool parts
  • Limits sets max_connections and keep-alive; PoolTimeout signals exhaustion
  • HTTPTransport(retries=n) only retries connection failures

Watch out for

  • Expecting HTTPTransport(retries=3) to retry 503 responses.

Check yourself

  1. You see many httpx.PoolTimeout errors under load. What is the most likely cause?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.python-httpx.org/advanced/resource-limits/