From a4e7cebf5c082fb718a74b5ae2d5617c05f0c877 Mon Sep 17 00:00:00 2001 From: DPR Date: Wed, 15 Nov 2023 09:17:51 +0800 Subject: [PATCH 1/2] gh-109538: Avoid RuntimeError when StreamWriter is deleted with closed loop (#111983) Issue a ResourceWarning instead. Co-authored-by: Hugo van Kemenade (cherry picked from commit e0f512797596282bff63260f8102592aad37cdf1) --- Lib/asyncio/streams.py | 7 ++- Lib/test/test_asyncio/test_streams.py | 59 +++++++++++++++++++ ...-11-11-16-42-48.gh-issue-109538.cMG5ux.rst | 1 + 3 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-11-11-16-42-48.gh-issue-109538.cMG5ux.rst diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index 7c407067e05a16..23b6e4c32f7c68 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -404,8 +404,11 @@ async def start_tls(self, sslcontext, *, def __del__(self): if not self._transport.is_closing(): - self.close() - + if self._loop.is_closed(): + warnings.warn("loop is closed", ResourceWarning) + else: + self.close() + warnings.warn(f"unclosed {self!r}", ResourceWarning) class StreamReader: diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 86354306f1fff3..5422dfe58063aa 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1067,6 +1067,65 @@ def test_eof_feed_when_closing_writer(self): self.assertEqual(messages, []) + def test_unclosed_resource_warnings(self): + async def inner(httpd): + rd, wr = await asyncio.open_connection(*httpd.address) + + wr.write(b'GET / HTTP/1.0\r\n\r\n') + data = await rd.readline() + self.assertEqual(data, b'HTTP/1.0 200 OK\r\n') + data = await rd.read() + self.assertTrue(data.endswith(b'\r\n\r\nTest message')) + 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 None: port = socket_helper.find_unused_port() diff --git a/Misc/NEWS.d/next/Library/2023-11-11-16-42-48.gh-issue-109538.cMG5ux.rst b/Misc/NEWS.d/next/Library/2023-11-11-16-42-48.gh-issue-109538.cMG5ux.rst new file mode 100644 index 00000000000000..d1ee4c054a3f19 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-11-11-16-42-48.gh-issue-109538.cMG5ux.rst @@ -0,0 +1 @@ +Issue warning message instead of having :class:`RuntimeError` be displayed when event loop has already been closed at :meth:`StreamWriter.__del__`. From c323eb5d7377afbf687015f01c70b69f85a8d58e Mon Sep 17 00:00:00 2001 From: DPR Date: Mon, 20 Nov 2023 14:14:48 +0800 Subject: [PATCH 2/2] improve test case --- Lib/test/test_asyncio/test_streams.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 5422dfe58063aa..f5decbe53dacb3 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1116,13 +1116,10 @@ async def inner(httpd): self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx)) with test_utils.run_test_server() as httpd: - try: + with self.assertRaises(RuntimeError): + # This exception is caused by `self.loop.stop()` as expected. self.loop.run_until_complete(inner(httpd)) - # This exception is caused by `self.loop.stop()` as expected. - except RuntimeError: - pass - finally: - gc.collect() + gc.collect() self.assertEqual(messages, [])