Skip to content

Commit d26bebd

Browse files
heliang666skumaraditya303
authored andcommitted
gh-135836: Fix IndexError in asyncio.create_connection with empty exceptions list (GH-135845)
(cherry picked from commit 0e19db6) Co-authored-by: heliang666s <147408835+heliang666s@users.noreply.github.com> Co-authored-by: Kumar Aditya <kumaraditya@python.org>
1 parent e3a277c commit d26bebd

File tree

3 files changed

+35
-1
lines changed

3 files changed

+35
-1
lines changed

Lib/asyncio/base_events.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1164,7 +1164,7 @@ async def create_connection(
11641164
raise ExceptionGroup("create_connection failed", exceptions)
11651165
if len(exceptions) == 1:
11661166
raise exceptions[0]
1167-
else:
1167+
elif exceptions:
11681168
# If they all have the same str(), raise one.
11691169
model = str(exceptions[0])
11701170
if all(str(exc) == model for exc in exceptions):
@@ -1173,6 +1173,9 @@ async def create_connection(
11731173
# the various error messages.
11741174
raise OSError('Multiple exceptions: {}'.format(
11751175
', '.join(str(exc) for exc in exceptions)))
1176+
else:
1177+
# No exceptions were collected, raise a timeout error
1178+
raise TimeoutError('create_connection failed')
11761179
finally:
11771180
exceptions = None
11781181

Lib/test/test_asyncio/test_base_events.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,6 +1190,36 @@ def getaddrinfo(*args, **kw):
11901190
self.loop.run_until_complete(coro)
11911191
self.assertTrue(sock.close.called)
11921192

1193+
@patch_socket
1194+
def test_create_connection_happy_eyeballs_empty_exceptions(self, m_socket):
1195+
# See gh-135836: Fix IndexError when Happy Eyeballs algorithm
1196+
# results in empty exceptions list
1197+
1198+
async def getaddrinfo(*args, **kw):
1199+
return [(socket.AF_INET, socket.SOCK_STREAM, 0, '', ('127.0.0.1', 80)),
1200+
(socket.AF_INET6, socket.SOCK_STREAM, 0, '', ('::1', 80))]
1201+
1202+
def getaddrinfo_task(*args, **kwds):
1203+
return self.loop.create_task(getaddrinfo(*args, **kwds))
1204+
1205+
self.loop.getaddrinfo = getaddrinfo_task
1206+
1207+
# Mock staggered_race to return empty exceptions list
1208+
# This simulates the scenario where Happy Eyeballs algorithm
1209+
# cancels all attempts but doesn't properly collect exceptions
1210+
with mock.patch('asyncio.staggered.staggered_race') as mock_staggered:
1211+
# Return (None, []) - no winner, empty exceptions list
1212+
async def mock_race(coro_fns, delay, loop):
1213+
return None, []
1214+
mock_staggered.side_effect = mock_race
1215+
1216+
coro = self.loop.create_connection(
1217+
MyProto, 'example.com', 80, happy_eyeballs_delay=0.1)
1218+
1219+
# Should raise TimeoutError instead of IndexError
1220+
with self.assertRaisesRegex(TimeoutError, "create_connection failed"):
1221+
self.loop.run_until_complete(coro)
1222+
11931223
def test_create_connection_host_port_sock(self):
11941224
coro = self.loop.create_connection(
11951225
MyProto, 'example.com', 80, sock=object())
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

0 commit comments

Comments
 (0)