[3.14] gh-135836: Fix IndexError in asyncio.create_connection with empty exceptions list (GH-135845) (#136167)

gh-135836: Fix `IndexError` in `asyncio.create_connection` with empty exceptions list (GH-135845)
(cherry picked from commit 0e19db653d)

Co-authored-by: heliang666s <147408835+heliang666s@users.noreply.github.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
This commit is contained in:
Miss Islington (bot) 2025-07-03 06:07:18 +02:00 committed by GitHub
parent b86d3f0d69
commit 8810ccfc60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 35 additions and 1 deletions

View file

@ -1161,7 +1161,7 @@ class BaseEventLoop(events.AbstractEventLoop):
raise ExceptionGroup("create_connection failed", exceptions)
if len(exceptions) == 1:
raise exceptions[0]
else:
elif exceptions:
# If they all have the same str(), raise one.
model = str(exceptions[0])
if all(str(exc) == model for exc in exceptions):
@ -1170,6 +1170,9 @@ class BaseEventLoop(events.AbstractEventLoop):
# the various error messages.
raise OSError('Multiple exceptions: {}'.format(
', '.join(str(exc) for exc in exceptions)))
else:
# No exceptions were collected, raise a timeout error
raise TimeoutError('create_connection failed')
finally:
exceptions = None

View file

@ -1190,6 +1190,36 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
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
# results in empty exceptions list
async def getaddrinfo(*args, **kw):
return [(socket.AF_INET, socket.SOCK_STREAM, 0, '', ('127.0.0.1', 80)),
(socket.AF_INET6, socket.SOCK_STREAM, 0, '', ('::1', 80))]
def getaddrinfo_task(*args, **kwds):
return self.loop.create_task(getaddrinfo(*args, **kwds))
self.loop.getaddrinfo = getaddrinfo_task
# Mock staggered_race to return empty exceptions list
# This simulates the scenario where Happy Eyeballs algorithm
# cancels all attempts but doesn't properly collect exceptions
with mock.patch('asyncio.staggered.staggered_race') as mock_staggered:
# Return (None, []) - no winner, empty exceptions list
async def mock_race(coro_fns, delay, loop):
return None, []
mock_staggered.side_effect = mock_race
coro = self.loop.create_connection(
MyProto, 'example.com', 80, happy_eyeballs_delay=0.1)
# Should raise TimeoutError instead of IndexError
with self.assertRaisesRegex(TimeoutError, "create_connection failed"):
self.loop.run_until_complete(coro)
def test_create_connection_host_port_sock(self):
coro = self.loop.create_connection(
MyProto, 'example.com', 80, sock=object())

View file

@ -0,0 +1 @@
Fix :exc:`IndexError` in :meth:`asyncio.loop.create_connection` that could occur when the Happy Eyeballs algorithm resulted in an empty exceptions list during connection attempts.