Describe the bug
TCPConnector.close() nullifies the AsyncResolver's internal _resolver reference before marking the connector as closed (_closed = True). When aiodns is installed (activating AsyncResolver as the default), an in-flight connection suspended in a trace callback resumes and calls self._resolver.resolve() on a None object:
AttributeError: 'NoneType' object has no attribute 'getaddrinfo'
The teardown order in TCPConnector.close() is:
async def close(self, *, abort_ssl: bool = False) -> None:
if self._resolver_owner:
await self._resolver.close() # (A) resolver._resolver = None
await super().close(abort_ssl=...) # (B) _closed = True
Between (A) and (B), _closed is still False and session.closed returns False, so callers have no way to detect the broken state. This only affects the non-cached resolve path (use_dns_cache=False) since the cached path's resolve task is tracked in _resolve_host_tasks and explicitly cancelled by _close_immediately.
To Reproduce
import asyncio
import sys
import aiohttp
async def main() -> None:
async def on_dns_start(session, ctx, params):
await asyncio.sleep(0)
trace = aiohttp.TraceConfig()
trace.on_dns_resolvehost_start.append(on_dns_start)
session = aiohttp.ClientSession(
trace_configs=[trace],
connector=aiohttp.TCPConnector(use_dns_cache=False),
)
task = asyncio.create_task(session.ws_connect("wss://example.com"))
await asyncio.sleep(0) # let task advance to trace callback yield
await session.close()
try:
await task
except AttributeError as e:
print(f"REPRODUCED: {e}")
except Exception as e:
print(f"{type(e).__name__}: {e}")
if __name__ == "__main__":
print(f"Python {sys.version.split()[0]}, aiohttp {aiohttp.__version__}")
asyncio.run(main())
Expected behavior
The connection attempt should fail with ClientConnectionError("Connector is closed"), not AttributeError.
Logs/tracebacks
Python 3.11.15, aiohttp 3.13.5
REPRODUCED: 'NoneType' object has no attribute 'getaddrinfo'
Python Version
$ python --version
Python 3.11.15
and
$ python --version
Python 3.14.4
aiohttp Version
$ python -m pip show aiohttp
Name: aiohttp
Version: 3.13.5
multidict Version
$ python -m pip show multidict
Name: multidict
Version: 6.7.1
propcache Version
$ python -m pip show propcache
Name: propcache
Version: 0.5.2
yarl Version
$ python -m pip show yarl
Name: yarl
Version: 1.23.0
OS
Linux (Arch/CachyOS)
Related component
Client
Additional context
This is the same race identified in #11987, which was closed without a fix. This issue provides a deterministic reproduction and a proposed fix.
Discovered via intermittent AttributeError crashes in livekit-agents during STT stream reconnection. The STT node schedules a session.ws_connect() retry when its websocket closes unexpectedly. If this happens right before a call ends, the retry task kicks off just before session.close() runs during agent shutdown. The STT code checks session.closed before reconnecting, but that flag is still False when the resolver is already gone.
Code of Conduct
Describe the bug
TCPConnector.close()nullifies theAsyncResolver's internal_resolverreference before marking the connector as closed (_closed = True). Whenaiodnsis installed (activatingAsyncResolveras the default), an in-flight connection suspended in a trace callback resumes and callsself._resolver.resolve()on aNoneobject:The teardown order in
TCPConnector.close()is:Between (A) and (B),
_closedis stillFalseandsession.closedreturnsFalse, so callers have no way to detect the broken state. This only affects the non-cached resolve path (use_dns_cache=False) since the cached path's resolve task is tracked in_resolve_host_tasksand explicitly cancelled by_close_immediately.To Reproduce
Expected behavior
The connection attempt should fail with
ClientConnectionError("Connector is closed"), notAttributeError.Logs/tracebacks
Python Version
aiohttp Version
multidict Version
propcache Version
yarl Version
OS
Linux (Arch/CachyOS)
Related component
Client
Additional context
This is the same race identified in #11987, which was closed without a fix. This issue provides a deterministic reproduction and a proposed fix.
Discovered via intermittent
AttributeErrorcrashes in livekit-agents during STT stream reconnection. The STT node schedules asession.ws_connect()retry when its websocket closes unexpectedly. If this happens right before a call ends, the retry task kicks off just beforesession.close()runs during agent shutdown. The STT code checkssession.closedbefore reconnecting, but that flag is stillFalsewhen the resolver is already gone.Code of Conduct