]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-153133: Fix socket leak in error path of asyncio create_connection (#153134)
authorTimofei <128279579+deadlovelll@users.noreply.github.com>
Tue, 7 Jul 2026 19:23:17 +0000 (22:23 +0300)
committerGitHub <noreply@github.com>
Tue, 7 Jul 2026 19:23:17 +0000 (00:53 +0530)
Lib/asyncio/base_events.py
Lib/test/test_asyncio/test_base_events.py
Misc/NEWS.d/next/Library/2026-07-05-17-39-16.gh-issue-153133.kKSH7g.rst [new file with mode: 0644]

index 93bd7df993d8270aef2d812776a3ac451ff42edf..3836aaad326f62e282a0226cf60b541c7e267ac4 100644 (file)
@@ -1219,21 +1219,26 @@ class BaseEventLoop(events.AbstractEventLoop):
             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
index fa3821e0783858c67fe1812f12db2c4d7cc136c0..b4fd7abed5963ea622f8d18bba519f9c25da385b 100644 (file)
@@ -1282,6 +1282,47 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
                 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 (file)
index 0000000..a7a6815
--- /dev/null
@@ -0,0 +1,2 @@
+Fix a socket leak in :meth:`asyncio.loop.create_connection` when the
+transport cannot be created.