https://github.com/python/cpython/commit/c9e0918aad80ada2b4794bed64f117a1b295b7b7
commit: c9e0918aad80ada2b4794bed64f117a1b295b7b7
branch: 3.15
author: Timofei Ivankov <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-10-19T10:12:09+05:30
summary:

[3.15] gh-159049: Fix reference cycles after a failed TLS handshake in asyncio 
(#159682)

gh-159049: Fix reference cycles after a failed TLS handshake in asyncio

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 7b6a67023381fe..59c61f9ba657a1 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -1243,6 +1243,7 @@ async def _create_connection_transport(
             await waiter
         except:
             transport.close()
+            waiter = None
             raise
 
         return transport, protocol
@@ -1377,6 +1378,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 326d64025f3685..c335eaee7a7146 100644
--- a/Lib/asyncio/selector_events.py
+++ b/Lib/asyncio/selector_events.py
@@ -1222,6 +1222,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 455397024107e6..851dc6e127651c 100644
--- a/Lib/test/test_asyncio/test_sslproto.py
+++ b/Lib/test/test_asyncio/test_sslproto.py
@@ -824,6 +824,68 @@ async def client(addr):
             with self.assertRaises(ssl.SSLCertVerificationError):
                 self.loop.run_until_complete(client(srv.addr))
 
+    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_start_tls_client_corrupted_ssl(self):
         self.loop.set_exception_handler(lambda loop, ctx: None)
 
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 00000000000000..96f1cc3c0520d5
--- /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