Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
272d59c
avoid refcycles in happy eyeballs
graingert Oct 13, 2024
c217d95
update history
graingert Oct 13, 2024
9b46a18
can't clear oserrors
graingert Oct 13, 2024
222c6cd
specify only v4
graingert Oct 13, 2024
a2baf53
fix on trio on py < 3.13
graingert Oct 13, 2024
06c77dc
Merge branch 'master' into happy-eyeballs-cyclic-garbage
graingert Jan 7, 2025
2387f99
Apply suggestions from code review
graingert Jan 7, 2025
0203d03
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 7, 2025
6fba791
fixes after merge
graingert Jan 7, 2025
6dd2010
add ephemeral-port-reserve again
graingert Jan 7, 2025
877855e
Update pyproject.toml
graingert Jan 7, 2025
4bb150d
skip on 3.9 asyncio
graingert Jan 7, 2025
4eea30f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 7, 2025
7ddcd12
Update tests/test_sockets.py
graingert Jan 7, 2025
0542d94
news
graingert Jan 7, 2025
e53e9eb
Update tests/test_sockets.py
graingert Jan 7, 2025
3d626c9
remove ephemeral-port-reserve
graingert Jan 7, 2025
05d9f63
Update tests/test_sockets.py
graingert Jan 7, 2025
72c8b93
Merge branch 'master' into happy-eyeballs-cyclic-garbage
graingert Mar 12, 2025
fa77dd2
Update tests/test_sockets.py
graingert Mar 12, 2025
480b4f8
Merge branch 'master' into happy-eyeballs-cyclic-garbage
graingert Mar 12, 2025
c198531
Merge branch 'master' into happy-eyeballs-cyclic-garbage
graingert Mar 12, 2025
98f0db8
move no_other_refs
graingert Mar 12, 2025
a637d4b
use free_tcp_port
graingert Mar 12, 2025
d1d0e30
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 12, 2025
4fe9de3
fix ip NamedError
graingert Mar 12, 2025
3c99108
Reformatted the changelog entry
agronholm Mar 13, 2025
f167b6f
use free_tcp_port in test_connection_refused
graingert Mar 16, 2025
4f9ff5b
Update tests/test_sockets.py
graingert Mar 16, 2025
fe79118
Update src/anyio/_core/_sockets.py
graingert Mar 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
current Python version on several of its methods, and made the ``is_junction`` method
unavailable on Python versions earlier than 3.12
(`#794 <https://github.com/agronholm/anyio/issues/794>`_)
- Fixed connect_tcp producing cyclic references in tracebacks
when raising exceptions (`#809 <https://github.com/agronholm/anyio/pull/809>`_)
(PR by @graingert)


**4.6.0**

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ test = [
"""\
uvloop >= 0.21.0b1; platform_python_implementation == 'CPython' \
and platform_system != 'Windows'\
"""
""",
"ephemeral-port-reserve >= 1.1.4",
]
doc = [
"packaging",
Expand Down
31 changes: 17 additions & 14 deletions src/anyio/_core/_sockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,20 +211,23 @@ async def try_connect(remote_host: str, event: Event) -> None:
target_addrs = [(socket.AF_INET, addr_obj.compressed)]

oserrors: list[OSError] = []
async with create_task_group() as tg:
for i, (af, addr) in enumerate(target_addrs):
event = Event()
tg.start_soon(try_connect, addr, event)
with move_on_after(happy_eyeballs_delay):
await event.wait()

if connected_stream is None:
cause = (
oserrors[0]
if len(oserrors) == 1
else ExceptionGroup("multiple connection attempts failed", oserrors)
)
raise OSError("All connection attempts failed") from cause
try:
async with create_task_group() as tg:
for i, (af, addr) in enumerate(target_addrs):
event = Event()
tg.start_soon(try_connect, addr, event)
with move_on_after(happy_eyeballs_delay):
await event.wait()

if connected_stream is None:
cause = (
oserrors[0]
if len(oserrors) == 1
else ExceptionGroup("multiple connection attempts failed", oserrors)
)
raise OSError("All connection attempts failed") from cause
finally:
oserrors = []

if tls or tls_hostname or ssl_context:
try:
Expand Down
34 changes: 34 additions & 0 deletions tests/test_sockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from threading import Thread
from typing import Any, NoReturn, TypeVar, cast

import ephemeral_port_reserve
import psutil
import pytest
from _pytest.fixtures import SubRequest
Expand Down Expand Up @@ -125,6 +126,16 @@ def check_asyncio_bug(anyio_backend_name: str, family: AnyIPAddressFamily) -> No
pytest.skip("Does not work due to a known bug (39148)")


if sys.version_info <= (3, 11):

def no_other_refs() -> list[object]:
return [sys._getframe(1)]
else:

def no_other_refs() -> list[object]:
return []


_T = TypeVar("_T")


Expand Down Expand Up @@ -307,6 +318,29 @@ def serve() -> None:
server_sock.close()
assert client_addr[0] == expected_client_addr

@pytest.mark.skipif(
sys.implementation.name == "pypy",
reason=(
"gc.get_referrers is broken on PyPy see "
"https://github.com/pypy/pypy/issues/5075"
),
)
async def test_happy_eyeballs_refcycles(self) -> None:
"""
Test derived from https://github.com/python/cpython/pull/124859
"""
ip = "127.0.0.1"
port = ephemeral_port_reserve.reserve(ip=ip)
exc = None
try:
async with await connect_tcp(ip, port):
pass
except OSError as e:
exc = e.__cause__

assert isinstance(exc, OSError)
assert gc.get_referrers(exc) == no_other_refs()

@pytest.mark.parametrize(
"target, exception_class",
[
Expand Down
Loading