Skip to content

Commit

Permalink
[3.11] gh-109538: Catch closed loop runtime error and issue warning (G…
Browse files Browse the repository at this point in the history
…H-111983) (#112141)

Issue a ResourceWarning instead.

Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
(cherry picked from commit e0f5127)
  • Loading branch information
dpr-0 committed Nov 20, 2023
1 parent 6c51c84 commit 1397505
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 2 deletions.
7 changes: 5 additions & 2 deletions Lib/asyncio/streams.py
Expand Up @@ -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:

Expand Down
56 changes: 56 additions & 0 deletions Lib/test/test_asyncio/test_streams.py
Expand Up @@ -1067,6 +1067,62 @@ 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 <StreamWriter"))

messages = []
self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))

with test_utils.run_test_server() as httpd:
self.loop.run_until_complete(inner(httpd))

self.assertEqual(messages, [])

def test_loop_is_closed_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'))

# Make "loop is closed" occur first before "del wr" for this test.
self.loop.stop()
wr.close()
while not self.loop.is_closed():
await asyncio.sleep(0.0)

with self.assertWarns(ResourceWarning) as cm:
del wr
gc.collect()
self.assertEqual(len(cm.warnings), 1)
self.assertEqual("loop is closed", str(cm.warnings[0].message))

messages = []
self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))

with test_utils.run_test_server() as httpd:
with self.assertRaises(RuntimeError):
# This exception is caused by `self.loop.stop()` as expected.
self.loop.run_until_complete(inner(httpd))
gc.collect()

self.assertEqual(messages, [])

def test_unhandled_exceptions(self) -> None:
port = socket_helper.find_unused_port()

Expand Down
@@ -0,0 +1 @@
Issue warning message instead of having :class:`RuntimeError` be displayed when event loop has already been closed at :meth:`StreamWriter.__del__`.

0 comments on commit 1397505

Please sign in to comment.