@@ -261,61 +261,74 @@ def _assign_requests_to_connections(self) -> list[AsyncConnectionInterface]:
261261 Any closing connections are returned, allowing the I/O for closing
262262 those connections to be handled separately.
263263 """
264- closing_connections = []
264+ closing_connections : list [AsyncConnectionInterface ] = []
265+ retained_connections : list [AsyncConnectionInterface ] = []
265266
266- # First we handle cleaning up any connections that are closed,
267- # have expired their keep-alive, or surplus idle connections .
268- for connection in list ( self ._connections ) :
267+ # First we handle cleaning up any connections that are closed
268+ # or have expired their keep-alive, in a single pass .
269+ for connection in self ._connections :
269270 if connection .is_closed ():
270- # log: "removing closed connection"
271- self ._connections .remove (connection )
271+ continue
272272 elif connection .has_expired ():
273- # log: "closing expired connection"
274- self ._connections .remove (connection )
275- closing_connections .append (connection )
276- elif (
277- connection .is_idle ()
278- and sum (connection .is_idle () for connection in self ._connections ) > self ._max_keepalive_connections
279- ):
280- # log: "closing idle connection"
281- self ._connections .remove (connection )
282273 closing_connections .append (connection )
274+ else :
275+ retained_connections .append (connection )
276+
277+ # Then we close any surplus idle connections, to enforce the
278+ # max_keepalive_connections setting.
279+ idle_surplus = (
280+ sum (connection .is_idle () for connection in retained_connections ) - self ._max_keepalive_connections
281+ )
282+ if idle_surplus > 0 :
283+ kept : list [AsyncConnectionInterface ] = []
284+ for connection in retained_connections :
285+ if idle_surplus > 0 and connection .is_idle ():
286+ closing_connections .append (connection )
287+ idle_surplus -= 1
288+ else :
289+ kept .append (connection )
290+ retained_connections = kept
291+
292+ self ._connections = retained_connections
293+
294+ # Snapshot the set of reusable connections once, rather than rebuilding
295+ # it per queued request — this is what brings the loop from O(N*M) to
296+ # O(N+M) in the common case.
297+ available_connections = [connection for connection in self ._connections if connection .is_available ()]
298+ new_connection_budget = self ._max_connections - len (self ._connections )
283299
284300 # Assign queued requests to connections.
285- queued_requests = [request for request in self ._requests if request .is_queued ()]
286- for pool_request in queued_requests :
301+ for pool_request in self ._requests :
302+ if not pool_request .is_queued ():
303+ continue
287304 origin = pool_request .request .url .origin
288- available_connections = [
289- connection
290- for connection in self ._connections
291- if connection .can_handle_request (origin ) and connection .is_available ()
292- ]
293- idle_connections = [connection for connection in self ._connections if connection .is_idle ()]
294305
295306 # There are three cases for how we may be able to handle the request:
296307 #
297308 # 1. There is an existing connection that can handle the request.
298309 # 2. We can create a new connection to handle the request.
299310 # 3. We can close an idle connection and then create a new connection
300311 # to handle the request.
301- if available_connections :
302- # log: "reusing existing connection"
303- connection = available_connections [0 ]
304- pool_request .assign_to_connection (connection )
305- elif len (self ._connections ) < self ._max_connections :
306- # log: "creating new connection"
307- connection = self .create_connection (origin )
308- self ._connections .append (connection )
309- pool_request .assign_to_connection (connection )
310- elif idle_connections :
311- # log: "closing idle connection"
312- connection = idle_connections [0 ]
313- self ._connections .remove (connection )
314- closing_connections .append (connection )
315- # log: "creating new connection"
316- connection = self .create_connection (origin )
317- self ._connections .append (connection )
318- pool_request .assign_to_connection (connection )
312+ for connection in available_connections :
313+ if connection .can_handle_request (origin ):
314+ pool_request .assign_to_connection (connection )
315+ break
316+ else :
317+ if new_connection_budget > 0 :
318+ connection = self .create_connection (origin )
319+ self ._connections .append (connection )
320+ pool_request .assign_to_connection (connection )
321+ new_connection_budget -= 1
322+ continue
323+ for idx , connection in enumerate (available_connections ):
324+ if connection .is_idle ():
325+ del available_connections [idx ]
326+ self ._connections .remove (connection )
327+ closing_connections .append (connection )
328+ connection = self .create_connection (origin )
329+ self ._connections .append (connection )
330+ pool_request .assign_to_connection (connection )
331+ break
319332
320333 return closing_connections
321334
0 commit comments