Skip to content

IndexError when calling asyncio.open_connection #135836

Closed

Description

@vToMy

Bug report

Bug description:

I unfortunately don't have a minimal reproducible example, as it happens in a complex system under high concurrent load, but I get the following error after calling: await asyncio.open_connection(host, port)

File "/usr/local/lib/python3.13/asyncio/base_events.py", line 1169, in create_connection
  model = str(exceptions[0])
                 ~~~~~~~^^^
IndexError: list index out of range

Whatever the issue is, this is not proper error handling on python's part.

CPython versions tested on:

3.13

Operating systems tested on:

Linux

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Jun 23, 2025
  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Jun 23, 2025
  3. LamentXU123 commented on Jun 23, 2025

    @LamentXU123
    Contributor

    It seems like the exception list is empty. Could you add a breakpoint() in your complex system to see why? Some debugging information will be great caz we can't address the bug if we don't know what is going on in your system. the code that triggers the bug will be great.

  4. heliang666s commented on Jun 23, 2025

    @heliang666s
    Contributor

    Hi @LamentXU123 ,

    Found the root cause! The empty exceptions list occurs in Happy Eyeballs algorithm:

    Location: base_events.py:1169 in create_connection()

    Issue: When staggered_race() cancels slow connections, empty sublists remain in exceptions list. After flattening: [[], []] → [], then str(exceptions[0]) → IndexError.

    Fix:

    # Before (buggy):
    else:
        model = str(exceptions[0])  # IndexError on empty list
    
    # After (fixed):  
    elif len(exceptions) > 1:
        model = str(exceptions[0])  # Safe
    else:
        raise OSError('create_connection failed')  # Handle empty list

    Debug: Add print(f"exceptions={exceptions}") before line 1169 to see empty list.

    This happens with happy_eyeballs_delay parameter under concurrent load.

    I'm happy to submit a PR with the fix if you'd like. The change is minimal and maintains backward compatibility while preventing the IndexError crash in production environments.

    Best regards,

  5. LamentXU123 commented on Jun 23, 2025

    @LamentXU123
    Contributor

    Thanks! Raising a exception is proper there I think, but could we prevent the bug? (something like avoiding an empty list of exceptions) If yes that will be better tho. But if not I think correcting the exception is necessary.

    I am not the maintianer tho. cc @serhiy-storchaka

  6. added a commit that references this issue on Jun 23, 2025
  7. vToMy commented on Jun 23, 2025

    @vToMy
    Author

    I don't think the proposed PR is the proper way to solve it.
    I am not using happy_eyeballs_delay when I create the connection, and the default is None.

    It seems OSError is swallowed when not using happy eyeballs, evident by:

    # not using happy eyeballs
    for addrinfo in infos:
        try:
            sock = await self._connect_sock(
                exceptions, addrinfo, laddr_infos)
            break
        except OSError:
            continue

    This should somehow be properly handled in the above for-loop, or in _connect_sock (or both).

  8. LamentXU123 commented on Jun 23, 2025

    @LamentXU123
    Contributor

    I don't think the proposed PR is the proper way to solve it. I am not using happy_eyeballs_delay when I create the connection, and the default is None.

    In the PR they actually change it to TimeoutError, nvm

  9. serhiy-storchaka commented on Jun 23, 2025

    @serhiy-storchaka
    Member

    If _connect_sock() raises an OSError, it should also add it to exceptions. The only potential problem is if sock.close() fails -- that exception will not be added to the list. Can it be the cause of that case?

  10. vToMy commented on Jun 23, 2025

    @vToMy
    Author

    I think this is the problematic part:

    else:  # all bind attempts failed
        if my_exceptions:
            raise my_exceptions.pop()
        else:
            raise OSError(f"no matching local address with {family=} found")

    If my_exceptions is empty - the OSError raised is then swallowed in the loop (see my previous comment).
    If it has 1 item, that item is also swallowed since pop is called.
    If it has more than 1 item, the first item is discarded which is also not ideal.

  11. 16 remaining items

  12. added a commit that references this issue on Jul 3, 2025
  13. moved this from Todo to Done in asyncioon Jul 3, 2025
  14. added 2 commits that reference this issue on Jul 3, 2025
  15. added 2 commits that reference this issue on Jul 11, 2025
  16. added 2 commits that reference this issue on Jul 12, 2025
  17. added 2 commits that reference this issue on Jul 13, 2025
  18. added 2 commits that reference this issue on Aug 4, 2025
  19. added 2 commits that reference this issue on Aug 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-asynciotype-bugAn unexpected behavior, bug, or error

    Projects

    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions