Skip to content

Commit 1397505

Browse files
dpr-0hugovk
andauthored
[3.11] gh-109538: Catch closed loop runtime error and issue warning (GH-111983) (#112141)
Issue a ResourceWarning instead. Co-authored-by: Hugo van Kemenade <[email protected]> (cherry picked from commit e0f5127)
1 parent 6c51c84 commit 1397505

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

Lib/asyncio/streams.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,11 @@ async def start_tls(self, sslcontext, *,
404404

405405
def __del__(self):
406406
if not self._transport.is_closing():
407-
self.close()
408-
407+
if self._loop.is_closed():
408+
warnings.warn("loop is closed", ResourceWarning)
409+
else:
410+
self.close()
411+
warnings.warn(f"unclosed {self!r}", ResourceWarning)
409412

410413
class StreamReader:
411414

Lib/test/test_asyncio/test_streams.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,62 @@ def test_eof_feed_when_closing_writer(self):
10671067

10681068
self.assertEqual(messages, [])
10691069

1070+
def test_unclosed_resource_warnings(self):
1071+
async def inner(httpd):
1072+
rd, wr = await asyncio.open_connection(*httpd.address)
1073+
1074+
wr.write(b'GET / HTTP/1.0\r\n\r\n')
1075+
data = await rd.readline()
1076+
self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
1077+
data = await rd.read()
1078+
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
1079+
with self.assertWarns(ResourceWarning) as cm:
1080+
del wr
1081+
gc.collect()
1082+
self.assertEqual(len(cm.warnings), 1)
1083+
self.assertTrue(str(cm.warnings[0].message).startswith("unclosed <StreamWriter"))
1084+
1085+
messages = []
1086+
self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))
1087+
1088+
with test_utils.run_test_server() as httpd:
1089+
self.loop.run_until_complete(inner(httpd))
1090+
1091+
self.assertEqual(messages, [])
1092+
1093+
def test_loop_is_closed_resource_warnings(self):
1094+
async def inner(httpd):
1095+
rd, wr = await asyncio.open_connection(*httpd.address)
1096+
1097+
wr.write(b'GET / HTTP/1.0\r\n\r\n')
1098+
data = await rd.readline()
1099+
self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
1100+
data = await rd.read()
1101+
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
1102+
1103+
# Make "loop is closed" occur first before "del wr" for this test.
1104+
self.loop.stop()
1105+
wr.close()
1106+
while not self.loop.is_closed():
1107+
await asyncio.sleep(0.0)
1108+
1109+
with self.assertWarns(ResourceWarning) as cm:
1110+
del wr
1111+
gc.collect()
1112+
self.assertEqual(len(cm.warnings), 1)
1113+
self.assertEqual("loop is closed", str(cm.warnings[0].message))
1114+
1115+
messages = []
1116+
self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))
1117+
1118+
with test_utils.run_test_server() as httpd:
1119+
with self.assertRaises(RuntimeError):
1120+
# This exception is caused by `self.loop.stop()` as expected.
1121+
self.loop.run_until_complete(inner(httpd))
1122+
gc.collect()
1123+
1124+
self.assertEqual(messages, [])
1125+
10701126
def test_unhandled_exceptions(self) -> None:
10711127
port = socket_helper.find_unused_port()
10721128

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Issue warning message instead of having :class:`RuntimeError` be displayed when event loop has already been closed at :meth:`StreamWriter.__del__`.

0 commit comments

Comments
 (0)