https://github.com/python/cpython/commit/95e32ba8262b2f08d4c574bce55425589cbf9885
commit: 95e32ba8262b2f08d4c574bce55425589cbf9885
branch: main
author: Seungki Kim <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-08-18T19:06:16+05:30
summary:

gh-155941: Close the transport when client_connected_cb raises in asyncio 
(#155942)

files:
A Misc/NEWS.d/next/Library/2026-08-17-21-00-00.gh-issue-155941.strmCb.rst
M Lib/asyncio/streams.py
M Lib/test/test_asyncio/test_streams.py

diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py
index f5c4f0b0c3297ba..954e132617ce42b 100644
--- a/Lib/asyncio/streams.py
+++ b/Lib/asyncio/streams.py
@@ -239,7 +239,17 @@ def connection_made(self, transport):
         self._over_ssl = transport.get_extra_info('sslcontext') is not None
         if self._client_connected_cb is not None:
             writer = StreamWriter(transport, self, reader, self._loop)
-            res = self._client_connected_cb(reader, writer)
+            try:
+                res = self._client_connected_cb(reader, writer)
+            except Exception as exc:
+                self._loop.call_exception_handler({
+                    'message': 'Unhandled exception in client_connected_cb',
+                    'exception': exc,
+                    'transport': transport,
+                })
+                transport.close()
+                self._strong_reader = None
+                return
             if coroutines.iscoroutine(res):
                 def callback(task):
                     if task.cancelled():
diff --git a/Lib/test/test_asyncio/test_streams.py 
b/Lib/test/test_asyncio/test_streams.py
index 911087a128f9713..172f183849c3057 100644
--- a/Lib/test/test_asyncio/test_streams.py
+++ b/Lib/test/test_asyncio/test_streams.py
@@ -1267,6 +1267,38 @@ async def handle_echo(reader, writer):
         messages = self._basetest_unhandled_exceptions(handle_echo)
         self.assertEqual(messages, [])
 
+    def test_unhandled_exception_sync_callback(self):
+        # An exception raised by a plain-function client_connected_cb is
+        # reported like the coroutine case and the transport is closed.
+        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)
+            async with asyncio.timeout(60):
+                data = await rd.read()
+            self.assertEqual(data, b'')  # the server closed the connection
+            wr.close()
+            await wr.wait_closed()
+
+        async def main():
+            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')
+
     def test_open_connection_happy_eyeball_refcycles(self):
         port = socket_helper.find_unused_port()
         async def main():
diff --git 
a/Misc/NEWS.d/next/Library/2026-08-17-21-00-00.gh-issue-155941.strmCb.rst 
b/Misc/NEWS.d/next/Library/2026-08-17-21-00-00.gh-issue-155941.strmCb.rst
new file mode 100644
index 000000000000000..6cee7eb4eb9090e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-17-21-00-00.gh-issue-155941.strmCb.rst
@@ -0,0 +1,4 @@
+Fix :func:`asyncio.start_server` when a plain-function *client_connected_cb*
+raises: the error is now reported like the coroutine case and the transport
+is closed, instead of leaving the connection open forever (which also made
+:meth:`asyncio.Server.wait_closed` hang).

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to