8000 gh-135836: Fix IndexError in asyncio.create_connection with empty exceptions list by heliang666s · Pull Request #135845 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-135836: Fix IndexError in asyncio.create_connection with empty exceptions list #135845

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
5 changes: 4 additions & 1 deletion Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1161,7 +1161,7 @@ async def create_connection(
raise ExceptionGroup("create_connection failed", exceptions)
if len(exceptions) == 1:
raise exceptions[0]
else:
elif len(exceptions) > 1:
# If they all have the same str(), raise one.
model = str(exceptions[0])
if all(str(exc) == model for exc in exceptions):
Expand All @@ -1170,6 +1170,9 @@ async def create_connection(
# 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

Expand Down
31 changes: 31 additions & 0 deletions Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1190,6 +1190,37 @@ def getaddrinfo(*args, **kw):
self.loop.run_until_complete(coro)
self.assertTrue(sock.close.called)

@patch_socket
def test_create_connection_happy_eyeballs_empty_exceptions(self, m_socket):
# Test for gh-135836: Fix IndexError when Happy Eyeballs algorithm
# results in empty exceptions list
from unittest import mock

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.assertRaises(TimeoutError):
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())
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading
0