aio-libs/aiohttp · error · ClientConnectionError

Connector is closed.

Error message

Connector is closed.

What it means

Raised inside BaseConnector.connect() after _create_connection has succeeded, if self._closed became True during the await (another task called connector.close()). The freshly opened proto is closed and the request aborted with ClientConnectionError to avoid handing out a connection from a pool that is being torn down. This is a race between shutdown and a concurrent request.

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 c0ef574e29)

Solutions

  1. Order shutdown: await all in-flight requests (or cancel and await them) before calling session/connector.close().
  2. Use a lifespan/guard flag and reject new requests once shutdown has begun, rather than racing close().
  3. If seen during tests, ensure the test fixture closes the session after the request completes.

Example fix

# before
async def main():
    task = asyncio.create_task(session.get(url))
    await session.close()  # races with task
    return await task
# after
async def main():
    task = asyncio.create_task(session.get(url))
    resp = await task
    await session.close()
    return resp
Defensive patterns

Strategy: validation

Validate before calling

def is_connector_open(connector) -> bool:
    return not connector.closed

Try / catch

from aiohttp import ClientConnectionError
try:
    resp = await session.get(url)
except ClientConnectionError as e:
    if 'Connector is closed' in str(e):
        # session was shut down concurrently; recreate or give up
        ...
    raise

Prevention

When it happens

Trigger: One coroutine calls `await connector.close()` (or closes the ClientSession that owns the connector) while another is mid-`await session.get(...)` and has just finished establishing the TCP connection.

Common situations: Application shutdown ordering: closing the session while in-flight requests are still settling. Fixtures that close the connector in teardown concurrently with the test making a final call. Cancellation paths where cleanup runs before pending ops drain.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/bedb2eef88a49a8e.json. Report an issue: GitHub.