From 6739735d4e765cc71d0bcb96c822b6c39192f929 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 5 Jul 2026 17:41:48 +0300 Subject: [PATCH] gh-153133: Fix socket leak in asyncio create_connection --- Lib/asyncio/base_events.py | 39 ++++++++++-------- Lib/test/test_asyncio/test_base_events.py | 41 +++++++++++++++++++ ...-07-05-17-39-16.gh-issue-153133.kKSH7g.rst | 2 + 3 files changed, 65 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-05-17-39-16.gh-issue-153133.kKSH7g.rst diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 93bd7df993d8270..d0aa35a4289df48 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -1219,26 +1219,31 @@ 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) - + # gh-153133: close the socket if the transport is never created. + transport = None 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) + await waiter except: - transport.close() + if transport is None: + sock.close() + else: + transport.close() raise return transport, protocol 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.