Skip to content

Commit ebd457a

Browse files
dpr-0hugovk
authored andcommitted
pythongh-109538: Avoid RuntimeError when StreamWriter is deleted with closed loop (python#111983)
Issue a ResourceWarning instead. Co-authored-by: Hugo van Kemenade <[email protected]>
1 parent e475ce0 commit ebd457a

File tree

3 files changed

+45
-5
lines changed

3 files changed

+45
-5
lines changed

Lib/asyncio/streams.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,11 @@ async def start_tls(self, sslcontext, *,
406406

407407
def __del__(self, warnings=warnings):
408408
if not self._transport.is_closing():
409-
self.close()
410-
warnings.warn(f"unclosed {self!r}", ResourceWarning)
411-
409+
if self._loop.is_closed():
410+
warnings.warn("loop is closed", ResourceWarning)
411+
else:
412+
self.close()
413+
warnings.warn(f"unclosed {self!r}", ResourceWarning)
412414

413415
class StreamReader:
414416

Lib/test/test_asyncio/test_streams.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,10 +1082,11 @@ async def inner(httpd):
10821082
self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
10831083
data = await rd.read()
10841084
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
1085-
with self.assertWarns(ResourceWarning):
1085+
with self.assertWarns(ResourceWarning) as cm:
10861086
del wr
10871087
gc.collect()
1088-
1088+
self.assertEqual(len(cm.warnings), 1)
1089+
self.assertTrue(str(cm.warnings[0].message).startswith("unclosed <StreamWriter"))
10891090

10901091
messages = []
10911092
self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))
@@ -1095,6 +1096,42 @@ async def inner(httpd):
10951096

10961097
self.assertEqual(messages, [])
10971098

1099+
def test_loop_is_closed_resource_warnings(self):
1100+
async def inner(httpd):
1101+
rd, wr = await asyncio.open_connection(*httpd.address)
1102+
1103+
wr.write(b'GET / HTTP/1.0\r\n\r\n')
1104+
data = await rd.readline()
1105+
self.assertEqual(data, b'HTTP/1.0 200 OK\r\n')
1106+
data = await rd.read()
1107+
self.assertTrue(data.endswith(b'\r\n\r\nTest message'))
1108+
1109+
# Make "loop is closed" occur first before "del wr" for this test.
1110+
self.loop.stop()
1111+
wr.close()
1112+
while not self.loop.is_closed():
1113+
await asyncio.sleep(0.0)
1114+
1115+
with self.assertWarns(ResourceWarning) as cm:
1116+
del wr
1117+
gc.collect()
1118+
self.assertEqual(len(cm.warnings), 1)
1119+
self.assertEqual("loop is closed", str(cm.warnings[0].message))
1120+
1121+
messages = []
1122+
self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))
1123+
1124+
with test_utils.run_test_server() as httpd:
1125+
try:
1126+
self.loop.run_until_complete(inner(httpd))
1127+
# This exception is caused by `self.loop.stop()` as expected.
1128+
except RuntimeError:
1129+
pass
1130+
finally:
1131+
gc.collect()
1132+
1133+
self.assertEqual(messages, [])
1134+
10981135
def test_unhandled_exceptions(self) -> None:
10991136
port = socket_helper.find_unused_port()
11001137

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)