diff --git a/CHANGES/8660.misc.rst b/CHANGES/8660.misc.rst new file mode 100644 index 00000000000..8710063329e --- /dev/null +++ b/CHANGES/8660.misc.rst @@ -0,0 +1,3 @@ +Improved performance of :py:meth:`~aiohttp.ClientWebSocketResponse.receive` and :py:meth:`~aiohttp.web.WebSocketResponse.receive` when there is no timeout. -- by :user:`bdraco`. + +The timeout context manager is now avoided when there is no timeout as it accounted for up to 50% of the time spent in the :py:meth:`~aiohttp.ClientWebSocketResponse.receive` and :py:meth:`~aiohttp.web.WebSocketResponse.receive` methods. diff --git a/aiohttp/client_ws.py b/aiohttp/client_ws.py index 0ce8e7e8f8e..f7822df8645 100644 --- a/aiohttp/client_ws.py +++ b/aiohttp/client_ws.py @@ -291,6 +291,8 @@ async def close(self, *, code: int = WSCloseCode.OK, message: bytes = b"") -> bo return False async def receive(self, timeout: Optional[float] = None) -> WSMessage: + receive_timeout = timeout or self._timeout.ws_receive + while True: if self._waiting: raise RuntimeError("Concurrent call to receive() is not allowed") @@ -304,9 +306,14 @@ async def receive(self, timeout: Optional[float] = None) -> WSMessage: try: self._waiting = True try: - async with async_timeout.timeout( - timeout or self._timeout.ws_receive - ): + if receive_timeout: + # Entering the context manager and creating + # Timeout() object can take almost 50% of the + # run time in this loop so we avoid it if + # there is no read timeout. + async with async_timeout.timeout(receive_timeout): + msg = await self._reader.read() + else: msg = await self._reader.read() self._reset_heartbeat() finally: diff --git a/aiohttp/web_ws.py b/aiohttp/web_ws.py index e76716e6ae7..3b76ab8eead 100644 --- a/aiohttp/web_ws.py +++ b/aiohttp/web_ws.py @@ -509,6 +509,7 @@ async def receive(self, timeout: Optional[float] = None) -> WSMessage: loop = self._loop assert loop is not None + receive_timeout = timeout or self._receive_timeout while True: if self._waiting: raise RuntimeError("Concurrent call to receive() is not allowed") @@ -524,7 +525,14 @@ async def receive(self, timeout: Optional[float] = None) -> WSMessage: try: self._waiting = True try: - async with async_timeout.timeout(timeout or self._receive_timeout): + if receive_timeout: + # Entering the context manager and creating + # Timeout() object can take almost 50% of the + # run time in this loop so we avoid it if + # there is no read timeout. + async with async_timeout.timeout(receive_timeout): + msg = await self._reader.read() + else: msg = await self._reader.read() self._reset_heartbeat() finally: