]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.11] [3.12] GH-110894: Call loop exception handler for exceptions in client_connect...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Sat, 4 Nov 2023 01:23:01 +0000 (02:23 +0100)
committerGitHub <noreply@github.com>
Sat, 4 Nov 2023 01:23:01 +0000 (01:23 +0000)
* [3.12] GH-110894: Call loop exception handler for exceptions in client_connected_cb (GH-111601) (GH-111632)
(cherry picked from commit 9aa88290d82e2808eed84e7a63d0bf9623f84f53)

Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Call loop exception handler for exceptions in `client_connected_cb` of `asyncio.start_server` so that applications can handle it..
(cherry picked from commit 229f44d353c71185414a072017f46f125676bdd6)

* gh-111644: Fix asyncio test_unhandled_exceptions() (#111713)

Fix test_unhandled_exceptions() of test_asyncio.test_streams: break
explicitly a reference cycle.

Fix also StreamTests.tearDown(): the loop must not be closed
explicitly, but using set_event_loop() which takes care of shutting
down the executor with executor.shutdown(wait=True).
BaseEventLoop.close() calls executor.shutdown(wait=False).

(cherry picked from commit ac01e2243a1104b2154c0d1bdbc9f8d5b3ada778)

---------

Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Co-authored-by: Victor Stinner <vstinner@python.org>
Lib/asyncio/streams.py
Lib/test/test_asyncio/test_streams.py
Misc/NEWS.d/next/Library/2023-11-01-14-03-24.gh-issue-110894.7-wZxC.rst [new file with mode: 0644]

index 19d9154b66d027ff6869e78fe1d8e01e245d81b7..7c407067e05a160b79f604f4b5f1917cd02efd76 100644 (file)
@@ -245,7 +245,19 @@ class StreamReaderProtocol(FlowControlMixin, protocols.Protocol):
             res = self._client_connected_cb(reader,
                                             self._stream_writer)
             if coroutines.iscoroutine(res):
+                def callback(task):
+                    exc = task.exception()
+                    if exc is not None:
+                        self._loop.call_exception_handler({
+                            'message': 'Unhandled exception in client_connected_cb',
+                            'exception': exc,
+                            'transport': transport,
+                        })
+                        transport.close()
+
                 self._task = self._loop.create_task(res)
+                self._task.add_done_callback(callback)
+
             self._strong_reader = None
 
     def connection_lost(self, exc):
index 95fc7a159baee8e078ba41708813282315292606..86354306f1fff37ef0cba64c1f8ff16bd39e930d 100644 (file)
@@ -36,8 +36,7 @@ class StreamTests(test_utils.TestCase):
         # just in case if we have transport close callbacks
         test_utils.run_briefly(self.loop)
 
-        self.loop.close()
-        gc.collect()
+        # set_event_loop() takes care of closing self.loop in a safe way
         super().tearDown()
 
     def _basetest_open_connection(self, open_connection_fut):
@@ -1068,6 +1067,37 @@ os.close(fd)
 
         self.assertEqual(messages, [])
 
+    def test_unhandled_exceptions(self) -> None:
+        port = socket_helper.find_unused_port()
+
+        messages = []
+        self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx))
+
+        async def client():
+            rd, wr = await asyncio.open_connection('localhost', port)
+            wr.write(b'test msg')
+            await wr.drain()
+            wr.close()
+            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()
+            await client()
+            server.close()
+            await server.wait_closed()
+
+        self.loop.run_until_complete(main())
+
+        self.assertEqual(messages[0]['message'],
+                         'Unhandled exception in client_connected_cb')
+        # Break explicitly reference cycle
+        messages = None
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2023-11-01-14-03-24.gh-issue-110894.7-wZxC.rst b/Misc/NEWS.d/next/Library/2023-11-01-14-03-24.gh-issue-110894.7-wZxC.rst
new file mode 100644 (file)
index 0000000..c59fe6b
--- /dev/null
@@ -0,0 +1 @@
+Call loop exception handler for exceptions in ``client_connected_cb`` of :func:`asyncio.start_server` so that applications can handle it. Patch by Kumar Aditya.