Connection Pool

A client keeps its connections alive and reuses them. Reusing a connection avoids the cost of a fresh TCP connection and TLS handshake on every request, which is the main reason to construct one client and make many requests through it rather than creating a client per request.

Reuse Is Automatic

Connections are pooled by origin: the scheme, host, and port of the request URL. When a new request shares the origin of an idle connection, the client reuses it instead of opening a new one:

burl::client client(co_await capy::this_coro::executor, tls_ctx);

auto r1 = co_await client.get("https://example.com/a").as<std::string>();
// the next request to the same origin reuses the connection
auto r2 = co_await client.get("https://example.com/b").as<std::string>();

Tuning the Pool

Setting Effect

config::pool_idle_timeout

How long an idle connection stays usable. One idle longer than this is discarded rather than reused.

config::pool_max_idle_per_host

The cap on idle connections kept per origin. Beyond it, connections are closed instead of pooled.

The Connection’s Lifetime

A connection is held by the response while its body is being read, not by the client. It returns to the pool when the response is destroyed and only if it can be kept alive and the entire message has been received. The practical consequences:

  • Read the body to completion to enable reuse. A response abandoned with its body partly read cannot be pooled.

  • Reuse needs keep-alive. A response the server marks to close, or one on a connection that errored, is closed rather than pooled.

  • Hold a response only as long as needed. While a response is alive it holds its connection out of the pool. Letting it go promptly returns the connection sooner.

Next Steps

  • Responses — Reading the body to free the connection

  • Timeouts — Connect and I/O timeouts on new connections

  • Proxies — Establishing connections through a proxy