diff --git a/CHANGES/9406.misc.rst b/CHANGES/9406.misc.rst new file mode 100644 index 0000000000..0a0f7e7867 --- /dev/null +++ b/CHANGES/9406.misc.rst @@ -0,0 +1 @@ +Reduced memory required for timer objects created during the client request lifecycle -- by :user:`bdraco`. diff --git a/CHANGES/9407.misc.rst b/CHANGES/9407.misc.rst new file mode 100644 index 0000000000..d2a4e1e3ae --- /dev/null +++ b/CHANGES/9407.misc.rst @@ -0,0 +1 @@ +Reduced memory required for stream objects created during the client request lifecycle -- by :user:`bdraco`. diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index c077e2f498..6468b2de3c 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -751,6 +751,9 @@ async def _on_headers_request_sent( await trace.send_request_headers(method, url, headers) +_CONNECTION_CLOSED_EXCEPTION = ClientConnectionError("Connection closed") + + class ClientResponse(HeadersMixin): # Some of these attributes are None when created, # but will be set by the start() method. @@ -1106,7 +1109,7 @@ def _notify_content(self) -> None: content = self.content # content can be None here, but the types are cheated elsewhere. if content and content.exception() is None: # type: ignore[truthy-bool] - set_exception(content, ClientConnectionError("Connection closed")) + set_exception(content, _CONNECTION_CLOSED_EXCEPTION) self._released = True async def wait_for_close(self) -> None: diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index 8474bb735c..ec67abf5eb 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -596,6 +596,8 @@ def calculate_timeout_when( class TimeoutHandle: """Timeout handle""" + __slots__ = ("_timeout", "_loop", "_ceil_threshold", "_callbacks") + def __init__( self, loop: asyncio.AbstractEventLoop, @@ -644,11 +646,17 @@ def __call__(self) -> None: class BaseTimerContext(ContextManager["BaseTimerContext"]): + + __slots__ = () + def assert_timeout(self) -> None: """Raise TimeoutError if timeout has been exceeded.""" class TimerNoop(BaseTimerContext): + + __slots__ = () + def __enter__(self) -> BaseTimerContext: return self @@ -664,6 +672,8 @@ def __exit__( class TimerContext(BaseTimerContext): """Low resolution timeout context manager""" + __slots__ = ("_loop", "_tasks", "_cancelled", "_cancelling") + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: self._loop = loop self._tasks: List[asyncio.Task[Any]] = [] diff --git a/aiohttp/streams.py b/aiohttp/streams.py index d47ca014a1..b611dc198d 100644 --- a/aiohttp/streams.py +++ b/aiohttp/streams.py @@ -42,6 +42,9 @@ class EofStream(Exception): class AsyncStreamIterator(Generic[_T]): + + __slots__ = ("read_func",) + def __init__(self, read_func: Callable[[], Awaitable[_T]]) -> None: self.read_func = read_func @@ -59,6 +62,9 @@ async def __anext__(self) -> _T: class ChunkTupleAsyncStreamIterator: + + __slots__ = ("_stream",) + def __init__(self, stream: "StreamReader") -> None: self._stream = stream @@ -73,6 +79,9 @@ async def __anext__(self) -> Tuple[bytes, bool]: class AsyncStreamReaderMixin: + + __slots__ = () + def __aiter__(self) -> AsyncStreamIterator[bytes]: return AsyncStreamIterator(self.readline) # type: ignore[attr-defined] @@ -107,7 +116,25 @@ class StreamReader(AsyncStreamReaderMixin): """ - total_bytes = 0 + __slots__ = ( + "_protocol", + "_low_water", + "_high_water", + "_loop", + "_size", + "_cursor", + "_http_chunk_splits", + "_buffer", + "_buffer_offset", + "_eof", + "_waiter", + "_eof_waiter", + "_exception", + "_timer", + "_eof_callbacks", + "_eof_counter", + "total_bytes", + ) def __init__( self, @@ -134,6 +161,8 @@ def __init__( self._exception: Optional[Union[Type[BaseException], BaseException]] = None self._timer = TimerNoop() if timer is None else timer self._eof_callbacks: List[Callable[[], None]] = [] + self._eof_counter = 0 + self.total_bytes = 0 def __repr__(self) -> str: info = [self.__class__.__name__] @@ -505,6 +534,9 @@ def _read_nowait(self, n: int) -> bytes: class EmptyStreamReader(StreamReader): # lgtm [py/missing-call-to-init] + + __slots__ = ("_read_eof_chunk",) + def __init__(self) -> None: self._read_eof_chunk = False diff --git a/pyproject.toml b/pyproject.toml index c4f6587a75..8b83775c74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,3 +82,8 @@ skip = "pp*" [tool.codespell] skip = '.git,*.pdf,*.svg,Makefile,CONTRIBUTORS.txt,venvs,_build' ignore-words-list = 'te,assertIn' + +[tool.slotscheck] +# TODO(3.13): Remove aiohttp.helpers once https://github.com/python/cpython/pull/106771 +# is available in all supported cpython versions +exclude-modules = "(^aiohttp\\.helpers)"