From ed9bca5d96a6b7e4951b14a430d25a5456cd5be0 Mon Sep 17 00:00:00 2001 From: Pierre Ossman Date: Mon, 19 Feb 2024 15:47:18 +0100 Subject: [PATCH 1/2] gh-114914: Avoid keeping dead StreamWriter alive In some cases we might cause a StreamWriter to stay alive even when the application has dropped all references to it. This prevents us from doing automatical cleanup, and complaining that the StreamWriter wasn't properly closed. Fortunately, the extra reference was never actually used for anything so we can just drop it. --- Lib/asyncio/streams.py | 14 ++++------- Lib/test/test_asyncio/test_streams.py | 23 +++++++++++++++++++ ...-02-19-15-52-30.gh-issue-114914.M5-1d8.rst | 2 ++ 3 files changed, 29 insertions(+), 10 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-02-19-15-52-30.gh-issue-114914.M5-1d8.rst diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index df58b7a799a5ad..3fe52dbac25c91 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -201,7 +201,6 @@ def __init__(self, stream_reader, client_connected_cb=None, loop=None): # is established. self._strong_reader = stream_reader self._reject_connection = False - self._stream_writer = None self._task = None self._transport = None self._client_connected_cb = client_connected_cb @@ -214,10 +213,8 @@ def _stream_reader(self): return None return self._stream_reader_wr() - def _replace_writer(self, writer): + def _replace_transport(self, transport): loop = self._loop - transport = writer.transport - self._stream_writer = writer self._transport = transport self._over_ssl = transport.get_extra_info('sslcontext') is not None @@ -239,11 +236,8 @@ def connection_made(self, transport): reader.set_transport(transport) self._over_ssl = transport.get_extra_info('sslcontext') is not None if self._client_connected_cb is not None: - self._stream_writer = StreamWriter(transport, self, - reader, - self._loop) - res = self._client_connected_cb(reader, - self._stream_writer) + writer = StreamWriter(transport, self, reader, self._loop) + res = self._client_connected_cb(reader, writer) if coroutines.iscoroutine(res): def callback(task): if task.cancelled(): @@ -405,7 +399,7 @@ async def start_tls(self, sslcontext, *, ssl_handshake_timeout=ssl_handshake_timeout, ssl_shutdown_timeout=ssl_shutdown_timeout) self._transport = new_transport - protocol._replace_writer(self) + protocol._replace_transport(new_transport) def __del__(self, warnings=warnings): if not self._transport.is_closing(): diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 3c8cc5f3649180..313597b460f819 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1129,6 +1129,29 @@ async def inner(httpd): self.assertEqual(messages, []) + def test_unclosed_server_resource_warnings(self): + async def inner(rd, wr): + with self.assertWarns(ResourceWarning) as cm: + del wr + gc.collect() + self.assertEqual(len(cm.warnings), 1) + self.assertTrue(str(cm.warnings[0].message).startswith("unclosed Date: Tue, 27 Feb 2024 15:53:16 +0100 Subject: [PATCH 2/2] More reliable waiting for connection --- Lib/test/test_asyncio/test_streams.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 313597b460f819..d28d0b931c2903 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1131,6 +1131,7 @@ async def inner(httpd): def test_unclosed_server_resource_warnings(self): async def inner(rd, wr): + fut.set_result(True) with self.assertWarns(ResourceWarning) as cm: del wr gc.collect() @@ -1143,11 +1144,12 @@ async def outer(): addr = srv.sockets[0].getsockname() with socket.create_connection(addr): # Give the loop some time to notice the connection - await asyncio.sleep(0.1) + await fut messages = [] self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx)) + fut = self.loop.create_future() self.loop.run_until_complete(outer()) self.assertEqual(messages, [])