https://github.com/python/cpython/commit/374920a23d49130f446fe6369220bc548be57cd2
commit: 374920a23d49130f446fe6369220bc548be57cd2
branch: main
author: Timofei <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-07-08T00:53:17+05:30
summary:

gh-153133: Fix socket leak in error path of asyncio create_connection (#153134)

files:
A Misc/NEWS.d/next/Library/2026-07-05-17-39-16.gh-issue-153133.kKSH7g.rst
M Lib/asyncio/base_events.py
M Lib/test/test_asyncio/test_base_events.py

diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py
index 93bd7df993d8270..3836aaad326f62e 100644
--- a/Lib/asyncio/base_events.py
+++ b/Lib/asyncio/base_events.py
@@ -1219,21 +1219,26 @@ async def _create_connection_transport(
             ssl_handshake_timeout=None,
             ssl_shutdown_timeout=None, context=None):
 
-        sock.setblocking(False)
-        context = context if context is not None else 
contextvars.copy_context()
-
-        protocol = protocol_factory()
-        waiter = self.create_future()
-        if ssl:
-            sslcontext = None if isinstance(ssl, bool) else ssl
-            transport = self._make_ssl_transport(
-                sock, protocol, sslcontext, waiter,
-                server_side=server_side, server_hostname=server_hostname,
-                ssl_handshake_timeout=ssl_handshake_timeout,
-                ssl_shutdown_timeout=ssl_shutdown_timeout,
-                context=context)
-        else:
-            transport = self._make_socket_transport(sock, protocol, waiter, 
context=context)
+        try:
+            sock.setblocking(False)
+            context = context if context is not None else 
contextvars.copy_context()
+
+            protocol = protocol_factory()
+            waiter = self.create_future()
+            if ssl:
+                sslcontext = None if isinstance(ssl, bool) else ssl
+                transport = self._make_ssl_transport(
+                    sock, protocol, sslcontext, waiter,
+                    server_side=server_side, server_hostname=server_hostname,
+                    ssl_handshake_timeout=ssl_handshake_timeout,
+                    ssl_shutdown_timeout=ssl_shutdown_timeout,
+                    context=context)
+            else:
+                transport = self._make_socket_transport(sock, protocol, 
waiter, context=context)
+        except:
+            # gh-153133: close the socket if the transport is never created.
+            sock.close()
+            raise
 
         try:
             await waiter
diff --git a/Lib/test/test_asyncio/test_base_events.py 
b/Lib/test/test_asyncio/test_base_events.py
index fa3821e0783858c..b4fd7abed5963ea 100644
--- a/Lib/test/test_asyncio/test_base_events.py
+++ b/Lib/test/test_asyncio/test_base_events.py
@@ -1282,6 +1282,47 @@ def getaddrinfo(*args, **kw):
                 self.loop.run_until_complete(coro)
             self.assertTrue(sock.close.called)
 
+    def test_create_connection_sock_transport_error_closes_sock(self):
+        # gh-153133: a user-provided socket is closed if the transport is
+        # never created.
+        sock = mock.Mock()
+        sock.type = socket.SOCK_STREAM
+
+        def factory():
+            raise ZeroDivisionError
+
+        coro = self.loop.create_connection(factory, sock=sock)
+        with self.assertRaises(ZeroDivisionError):
+            self.loop.run_until_complete(coro)
+        self.assertTrue(sock.close.called)
+
+    @patch_socket
+    def test_create_connection_transport_error_closes_sock(self, m_socket):
+        # gh-153133: an internally created socket is closed if the transport
+        # is never created.
+        sock = mock.Mock()
+        m_socket.socket.return_value = sock
+
+        def getaddrinfo(*args, **kw):
+            fut = self.loop.create_future()
+            addr = (socket.AF_INET, socket.SOCK_STREAM, 0, '',
+                    ('127.0.0.1', 80))
+            fut.set_result([addr])
+            return fut
+        self.loop.getaddrinfo = getaddrinfo
+
+        async def sock_connect(sock, address):
+            return None
+
+        def factory():
+            raise ZeroDivisionError
+
+        with mock.patch.object(self.loop, 'sock_connect', sock_connect):
+            coro = self.loop.create_connection(factory, '127.0.0.1', 80)
+            with self.assertRaises(ZeroDivisionError):
+                self.loop.run_until_complete(coro)
+        self.assertTrue(sock.close.called)
+
     @patch_socket
     def test_create_connection_happy_eyeballs_empty_exceptions(self, m_socket):
         # See gh-135836: Fix IndexError when Happy Eyeballs algorithm
diff --git 
a/Misc/NEWS.d/next/Library/2026-07-05-17-39-16.gh-issue-153133.kKSH7g.rst 
b/Misc/NEWS.d/next/Library/2026-07-05-17-39-16.gh-issue-153133.kKSH7g.rst
new file mode 100644
index 000000000000000..a7a68158ac895e3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-05-17-39-16.gh-issue-153133.kKSH7g.rst
@@ -0,0 +1,2 @@
+Fix a socket leak in :meth:`asyncio.loop.create_connection` when the
+transport cannot be created.

_______________________________________________
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