diff --git a/CHANGES/13346.bugfix.rst b/CHANGES/13346.bugfix.rst new file mode 100644 index 00000000000..f074a7b9d21 --- /dev/null +++ b/CHANGES/13346.bugfix.rst @@ -0,0 +1 @@ +Avoid eagerly starting the request body writer when a request is sent from a different event loop than the :class:`~aiohttp.ClientSession`. diff --git a/aiohttp/client_reqrep.py b/aiohttp/client_reqrep.py index 88934197978..39566526fb5 100644 --- a/aiohttp/client_reqrep.py +++ b/aiohttp/client_reqrep.py @@ -982,7 +982,7 @@ async def _send(self, conn: "Connection") -> ClientResponse: task: asyncio.Task[None] | None if self._should_write(protocol): coro = self._write_bytes(writer, conn, self._get_content_length()) - if sys.version_info >= (3, 12): + if sys.version_info >= (3, 12) and self.loop is asyncio.get_running_loop(): # Optimization for Python 3.12, try to write # bytes immediately to avoid having to schedule # the task on the event loop. diff --git a/tests/test_client_request.py b/tests/test_client_request.py index 0c7fca410f4..558b2de2ef5 100644 --- a/tests/test_client_request.py +++ b/tests/test_client_request.py @@ -1070,6 +1070,29 @@ async def test_chunked_empty_body( resp.close() +async def test_send_does_not_eager_start_writer_from_another_loop( + conn: mock.Mock, make_client_request: _RequestMaker +) -> None: + req = make_client_request( + "post", + URL("http://python.org/"), + data=b"foo", + loop=asyncio.get_running_loop(), + ) + other_loop = mock.Mock() + with ( + mock.patch( + "aiohttp.client_reqrep.asyncio.get_running_loop", return_value=other_loop + ), + mock.patch("aiohttp.client_reqrep.asyncio.Task") as eager_task, + ): + resp = await req._send(conn) + + eager_task.assert_not_called() + await req._close() + resp.close() + + async def test_chunked_explicit( conn: mock.Mock, make_client_request: _RequestMaker ) -> None: