aio-libs/aiohttp · error · ClientConnectionError

Connector is closed.

Error message

Connector is closed.

What it means

Raised near the end of connect() after a connection was successfully created but the connector was closed in the meantime (self._closed became True during an await). aiohttp closes the freshly created proto and raises ClientConnectionError so the caller does not receive a transport bound to a dead pool. It is a race between concurrent close() and a pending request.

Solutions

  1. Keep the connector/session open until every in-flight request has resolved.
  2. Use a single owned session per long-lived client instead of repeatedly closing and recreating.
  3. Before issuing requests, guard with `if connector.closed: build a new one`.
  4. Drive teardown via asyncio.gather(*pending) then close, so no connect is left mid-flight.

Example fix

# before
async with session:
    task = asyncio.create_task(session.get(url))
session.close()  # connector closed while task still connecting
# after
task = asyncio.create_task(session.get(url))
await task
await session.close()
Defensive patterns

Strategy: try-catch

Validate before calling

async def request_with_fresh_connector(session_factory, method, url):
    try:
        return await session_factory().request(method, url)
    except aiohttp.ClientConnectionError as exc:
        if 'Connector is closed' in str(exc):
            return await session_factory(force_new=True).request(method, url)
        raise

Type guard

null

Try / catch

try:
    resp = await session.get(url)
except aiohttp.ClientConnectionError as exc:
    if 'closed' in str(exc).lower():
        session = await new_session()  # rebuild connector + session
        resp = await session.get(url)
    else:
        raise

Prevention

When it happens

Trigger: Calling session.close() (which closes the connector) while a request is still connecting; closing a connector shared across tasks before all in-flight requests finish; context-manager exit racing with an in-flight connect.

Common situations: Using async with session outside the scope of pending tasks; an error handler that closes the connector aggressively; reconnect logic that recycles connectors too eagerly; tests that tear down the event loop while connections are still being established.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/bedb2eef88a49a8e. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/connector.py:699

            try:
                # Traces are done inside the try block to ensure that the
                # that the placeholder is still cleaned up if an exception
                # is raised.
                if traces:
                    for trace in traces:
                        await trace.send_connection_create_start()
                proto = await self._create_connection(req, traces, timeout)
                if traces:
                    for trace in traces:
                        await trace.send_connection_create_end()
            except BaseException:
                self._release_acquired(key, placeholder)
                raise
            else:
                if self._closed:
                    proto.close()
                    raise ClientConnectionError("Connector is closed.")

        # The connection was successfully created, drop the placeholder
        # and add the real connection to the acquired set. There should
        # be no awaits after the proto is added to the acquired set
        # to ensure that the connection is not left in the acquired set
        # on cancellation.
        self._acquired.remove(placeholder)
        self._acquired.add(proto)
        if self._limit_per_host:
            acquired_per_host = self._acquired_per_host[key]
            acquired_per_host.remove(placeholder)
            acquired_per_host.add(proto)
        return Connection(self, key, proto, self._loop)

    async def _wait_for_available_connection(
        self, key: "ConnectionKey", traces: list["Trace"]
    ) -> None:
        """Wait for an available connection slot."""

View on GitHub (pinned to d041d4d0fd)