https://github.com/python/cpython/commit/7dbc7975db8e34db8be8e4a10bd10784ab49b07a
commit: 7dbc7975db8e34db8be8e4a10bd10784ab49b07a
branch: main
author: Timofei Ivankov <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-10-18T21:55:24+05:30
summary:

gh-159049: Fix reference cycles after a failed TLS handshake in asyncio 
(#159140)

files:
A Misc/NEWS.d/next/Library/2026-10-10-22-29-13.gh-issue-159049.eJUiKL.rst
M Lib/asyncio/base_events.py
M Lib/asyncio/selector_events.py
M Lib/test/test_asyncio/test_sslproto.py

diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 90269c936555cb5..9eb34c5f29fb551 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -1244,6 +1244,7 @@ async def _create_connection_transport(
             await waiter
         except:
             transport.close()
+            waiter = None
             raise
 
         return transport, protocol
@@ -1381,6 +1382,7 @@ async def start_tls(self, transport, protocol, 
sslcontext, *,
             transport.close()
             conmade_cb.cancel()
             resume_cb.cancel()
+            waiter = None
             raise
 
         return ssl_protocol._app_transport
diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py
index bf20f053ded4353..f41fa47b891d4b1 100644
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -1225,6 +1225,7 @@ def _call_connection_lost(self, exc):
             super()._call_connection_lost(exc)
         finally:
             self._write_ready = None
+            self._read_ready_cb = None
             if self._empty_waiter is not None:
                 self._empty_waiter.set_exception(
                     ConnectionError("Connection is closed by peer"))
diff --git a/Lib/test/test_asyncio/test_sslproto.py 
b/Lib/test/test_asyncio/test_sslproto.py
index 253a470680ff052..acb80c19c0bdd0a 100644
--- a/Lib/test/test_asyncio/test_sslproto.py
+++ b/Lib/test/test_asyncio/test_sslproto.py
@@ -832,6 +832,68 @@ async def client(addr):
         self.assertIsInstance(server_err, ssl.SSLError)
         self.assertIn('ALERT_UNKNOWN_CA', server_err.reason or '')
 
+    def test_create_connection_ssl_failed_certificate_no_cycles(self):
+        # gh-159049
+        sslctx = test_utils.simple_server_sslcontext()
+        client_sslctx = test_utils.simple_client_sslcontext(
+            disable_verify=False)
+        transports = []
+
+        def server(sock):
+            try:
+                sock.start_tls(sslctx, server_side=True)
+            except OSError:
+                pass
+
+        connection_made = sslproto.SSLProtocol.connection_made
+
+        def remember_transport(proto, tr):
+            transports.append(weakref.ref(tr))
+            connection_made(proto, tr)
+
+        async def conn(addr):
+            try:
+                await self.loop.create_connection(
+                    asyncio.Protocol, *addr,
+                    ssl=client_sslctx, server_hostname='')
+            except ssl.SSLCertVerificationError:
+                pass
+
+        support.gc_collect()
+        with (self.tcp_server(server) as srv, support.disable_gc(),
+              mock.patch.object(sslproto.SSLProtocol, 'connection_made',
+                                remember_transport)):
+            self.loop.run_until_complete(conn(srv.addr))
+            self.assertEqual([tr() for tr in transports], [None])
+
+    def test_start_tls_failed_certificate_no_cycles(self):
+        # gh-159049
+        sslctx = test_utils.simple_server_sslcontext()
+        client_sslctx = test_utils.simple_client_sslcontext(
+            disable_verify=False)
+        transports = []
+
+        def server(sock):
+            try:
+                sock.start_tls(sslctx, server_side=True)
+            except OSError:
+                pass
+
+        async def client(addr):
+            tr, proto = await self.loop.create_connection(
+                asyncio.Protocol, *addr)
+            transports.append(weakref.ref(tr))
+            try:
+                await self.loop.start_tls(
+                    tr, proto, client_sslctx, server_hostname='')
+            except ssl.SSLCertVerificationError:
+                pass
+
+        support.gc_collect()
+        with self.tcp_server(server) as srv, support.disable_gc():
+            self.loop.run_until_complete(client(srv.addr))
+            self.assertEqual([tr() for tr in transports], [None])
+
     def test_create_server_ssl_failed_handshake_sends_alert(self):
         # gh-98078: when the handshake fails, the server must send the
         # fatal TLS alert generated by OpenSSL to the client before
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-10-22-29-13.gh-issue-159049.eJUiKL.rst 
b/Misc/NEWS.d/next/Library/2026-10-10-22-29-13.gh-issue-159049.eJUiKL.rst
new file mode 100644
index 000000000000000..96f1cc3c0520d57
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-10-22-29-13.gh-issue-159049.eJUiKL.rst
@@ -0,0 +1,3 @@
+Fix reference cycles in :meth:`asyncio.loop.create_connection` and
+:meth:`asyncio.loop.start_tls` after a failed TLS handshake.
+Patch by Timofei Ivankov.

_______________________________________________
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