From 8eb5048bf8379f42c5299ae93be91747123a8310 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 3 Jan 2024 11:20:30 -0800 Subject: [PATCH 1/3] Don't crash in stream reader protocol callback when task is cancelled --- Lib/asyncio/streams.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index ffb48b9cd6cb70..df58b7a799a5ad 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -246,6 +246,9 @@ def connection_made(self, transport): self._stream_writer) if coroutines.iscoroutine(res): def callback(task): + if task.cancelled(): + transport.close() + return exc = task.exception() if exc is not None: self._loop.call_exception_handler({ From f0a4893f731db5405996226d80d10c0e41952e91 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 3 Jan 2024 14:19:32 -0800 Subject: [PATCH 2/3] Blurb --- .../Library/2024-01-03-14-19-26.gh-issue-113538.ahuBCo.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2024-01-03-14-19-26.gh-issue-113538.ahuBCo.rst diff --git a/Misc/NEWS.d/next/Library/2024-01-03-14-19-26.gh-issue-113538.ahuBCo.rst b/Misc/NEWS.d/next/Library/2024-01-03-14-19-26.gh-issue-113538.ahuBCo.rst new file mode 100644 index 00000000000000..a52076501b7bf4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-01-03-14-19-26.gh-issue-113538.ahuBCo.rst @@ -0,0 +1,5 @@ +In :meth:`asyncio.StreamReaderProtocol.connection_made`, there is callback +that logs an error if the task wrapping the "connected callback" fails. This +callback would itself fail if the task was cancelled. Prevent this by +checking whether the task was cancelled first. If so, close the transport +but don't log an error. From 581df3aeed61ccc818724aecb5e530c7e28d5cb5 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 4 Jan 2024 09:26:02 -0800 Subject: [PATCH 3/3] Add test --- Lib/test/test_asyncio/test_streams.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index b408cd1f7da205..3c8cc5f3649180 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -1129,7 +1129,7 @@ async def inner(httpd): self.assertEqual(messages, []) - def test_unhandled_exceptions(self) -> None: + def _basetest_unhandled_exceptions(self, handle_echo): port = socket_helper.find_unused_port() messages = [] @@ -1143,9 +1143,6 @@ async def client(): await wr.wait_closed() async def main(): - async def handle_echo(reader, writer): - raise Exception('test') - server = await asyncio.start_server( handle_echo, 'localhost', port) await server.start_serving() @@ -1154,11 +1151,20 @@ async def handle_echo(reader, writer): await server.wait_closed() self.loop.run_until_complete(main()) + return messages + def test_unhandled_exception(self): + async def handle_echo(reader, writer): + raise Exception('test') + messages = self._basetest_unhandled_exceptions(handle_echo) self.assertEqual(messages[0]['message'], - 'Unhandled exception in client_connected_cb') - # Break explicitly reference cycle - messages = None + 'Unhandled exception in client_connected_cb') + + def test_unhandled_cancel(self): + async def handle_echo(reader, writer): + asyncio.current_task().cancel() + messages = self._basetest_unhandled_exceptions(handle_echo) + self.assertEqual(messages, []) if __name__ == '__main__':