perf: Rewrite `_assign_requests_to_connections` as a single-pass loop
Merged
MB
5mo ago+182+182+012345678901234567890123456789-82-82-01234567890123456789
The previous implementation re-scanned self._connections per queued request to rebuild available_connections and idle_connections lists, giving O(N*M) behaviour when many requests are queued against a populated pool. The keepalive-eviction step in pass 1 was also quadratic, since sum(... is_idle() ...) was recomputed for every connection.
This rewrite:
- Walks
self._connectionsonce to drop closed connections and schedule expired ones for close. - Counts idle connections once, then evicts surplus idle connections in a single pass to enforce
max_keepalive_connections. - Snapshots
available_connectionsonce before the queued-request loop, and consults it (rather than re-filteringself._connections) for each request.
Behaviour is preserved exactly:
- The same connection may still be assigned to multiple HTTP/1.1 requests in a single call. The pool's existing
ConnectionNotAvailableretry loop inhandle_async_requesthandles that case as before. - HTTP/2 multiplexing onto a single connection is preserved for the same reason — connections are not removed from
available_connectionson assignment.
Inspired by encode/httpcore#1035 by @VictorPrins.
Citing some issues related to this, including in downstream libraries relying on httpcore: