From d684195bb5cb90a85a07b98127a298f3b1ae2ef3 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Oct 2024 12:02:54 -0500 Subject: [PATCH] Speed up the ConnectionKey (#9365) --- CHANGES/9365.breaking.rst | 1 + aiohttp/client_reqrep.py | 24 +++++++++++++++--------- aiohttp/connector.py | 5 ++--- 3 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 CHANGES/9365.breaking.rst diff --git a/CHANGES/9365.breaking.rst b/CHANGES/9365.breaking.rst new file mode 100644 index 00000000000..f0224170f07 --- /dev/null +++ b/CHANGES/9365.breaking.rst @@ -0,0 +1 @@ +Changed ``ClientRequest.connection_key`` to be a `NamedTuple` to improve client performance -- by :user:`bdraco`. diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 499ca012a88..b8ea586ead5 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -19,6 +19,7 @@ Iterable, List, Mapping, + NamedTuple, Optional, Tuple, Type, @@ -150,8 +151,13 @@ def check(self, transport: asyncio.Transport) -> None: SSL_ALLOWED_TYPES = (bool,) -@dataclasses.dataclass(frozen=True) -class ConnectionKey: +_SSL_SCHEMES = frozenset(("https", "wss")) + + +# ConnectionKey is a NamedTuple because it is used as a key in a dict +# and a set in the connector. Since a NamedTuple is a tuple it uses +# the fast native tuple __hash__ and __eq__ implementation in CPython. +class ConnectionKey(NamedTuple): # the key should contain an information about used proxy / TLS # to prevent reusing wrong connections from a pool host: str @@ -287,7 +293,7 @@ def _writer(self, writer: Optional["asyncio.Task[None]"]) -> None: writer.add_done_callback(self.__reset_writer) def is_ssl(self) -> bool: - return self.url.scheme in ("https", "wss") + return self.url.scheme in _SSL_SCHEMES @property def ssl(self) -> Union["SSLContext", bool, Fingerprint]: @@ -295,16 +301,16 @@ def ssl(self) -> Union["SSLContext", bool, Fingerprint]: @property def connection_key(self) -> ConnectionKey: - proxy_headers = self.proxy_headers - if proxy_headers: + if proxy_headers := self.proxy_headers: h: Optional[int] = hash(tuple(proxy_headers.items())) else: h = None + url = self.url return ConnectionKey( - self.host, - self.port, - self.is_ssl(), - self.ssl, + url.raw_host or "", + url.port, + url.scheme in _SSL_SCHEMES, + self._ssl, self.proxy, self.proxy_auth, h, diff --git a/aiohttp/connector.py b/aiohttp/connector.py index 45f766b0fcc..1237f3c74d4 100644 --- a/aiohttp/connector.py +++ b/aiohttp/connector.py @@ -1,5 +1,4 @@ import asyncio -import dataclasses import functools import logging import random @@ -1312,8 +1311,8 @@ async def _create_proxy_connection( # asyncio handles this perfectly proxy_req.method = hdrs.METH_CONNECT proxy_req.url = req.url - key = dataclasses.replace( - req.connection_key, proxy=None, proxy_auth=None, proxy_headers_hash=None + key = req.connection_key._replace( + proxy=None, proxy_auth=None, proxy_headers_hash=None ) conn = Connection(self, key, proto, self._loop) proxy_resp = await proxy_req.send(conn)