Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions newsfragments/1249.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`trio.socket.getaddrinfo` no longer retries in a worker thread or calls the custom hostname resolver when the caller already passed both ``AI_NUMERICHOST`` and ``AI_NUMERICSERV``; the ``EAI_NONAME`` error from the non-blocking attempt is raised directly.
4 changes: 3 additions & 1 deletion src/trio/_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,13 @@ async def getaddrinfo(
# If host and port are numeric, then getaddrinfo doesn't block and we can
# skip the whole thread thing, which seems worthwhile. So we try first
# with the _NUMERIC_ONLY flags set, and then only spawn a thread if that
# fails with EAI_NONAME:
# fails with EAI_NONAME. If the caller already asked for _NUMERIC_ONLY,
# a retry would make the exact same call, so let the error propagate:
def numeric_only_failure(exc: BaseException) -> bool:
return (
isinstance(exc, _stdlib_socket.gaierror)
and exc.errno == _stdlib_socket.EAI_NONAME
and flags & _NUMERIC_ONLY != _NUMERIC_ONLY
)

async with _try_sync(numeric_only_failure):
Expand Down
27 changes: 27 additions & 0 deletions src/trio/_tests/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,33 @@ async def getnameinfo(
assert await tsocket.getaddrinfo("host", "port") == "x"


async def test_getaddrinfo_numeric_only_flags_skip_resolver() -> None:
# If the caller already passed AI_NUMERICHOST|AI_NUMERICSERV, the fast
# path call is identical to the one a worker thread or custom resolver
# would make, so its EAI_NONAME failure must propagate directly.
calls: list[tuple[object, ...]] = []

class RecordingResolver:
async def getaddrinfo(self, *args: object) -> list[object]:
calls.append(args)
return []

async def getnameinfo(self, *args: object) -> tuple[str, str]:
raise NotImplementedError

resolver = RecordingResolver()
assert await resolver.getaddrinfo(b"example.invalid", 80) == []
seen = len(calls)
tsocket.set_custom_hostname_resolver(resolver) # type: ignore[arg-type]
try:
with pytest.raises(tsocket.gaierror) as excinfo:
await tsocket.getaddrinfo("example.invalid", 80, flags=_NUMERIC_ONLY)
assert excinfo.value.errno == tsocket.EAI_NONAME
assert len(calls) == seen
finally:
tsocket.set_custom_hostname_resolver(None)


async def test_custom_socket_factory() -> None:
class CustomSocketFactory:
def socket(
Expand Down
Loading