aio-libs/aiohttp · error · RuntimeError

Session and connector have to use same event loop

Error message

Session and connector have to use same event loop

What it means

Raised in ClientSession.__init__ (client.py:377) when the user-supplied `connector` was bound to a different asyncio event loop than the one currently running when the session is constructed. aiohttp pins the connector to its loop at creation; the session refuses to operate across loops to avoid cross-loop I/O corruption. The guard compares `connector._loop` to the loop returned by `asyncio.get_running_loop()` at session construction time.

Source

Thrown at aiohttp/client.py:377

                DeprecationWarning,
                stacklevel=2,
            )

        if connector is None:
            connector = TCPConnector(ssl_shutdown_timeout=ssl_shutdown_timeout)
        # Initialize these three attrs before raising any exception,
        # they are used in __del__
        self._connector = connector
        self._loop = loop
        if loop.get_debug():
            self._source_traceback: traceback.StackSummary | None = (
                traceback.extract_stack(sys._getframe(1))
            )
        else:
            self._source_traceback = None

        if connector._loop is not loop:
            raise RuntimeError("Session and connector have to use same event loop")

        if cookie_jar is None:
            cookie_jar = CookieJar()
        self._cookie_jar = cookie_jar

        if cookies:
            self._cookie_jar.update_cookies(cookies)

        self._connector_owner = connector_owner
        self._version = version
        self._json_serialize = json_serialize
        self._json_serialize_bytes = json_serialize_bytes
        self._raise_for_status = raise_for_status
        self._auto_decompress = auto_decompress
        self._trust_env = trust_env
        self._requote_redirect_url = requote_redirect_url
        self._read_bufsize = read_bufsize
        self._max_line_size = max_line_size

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Construct both the connector and the session inside the same `async` function / same event loop.
  2. Drop the explicit `connector=` argument and let the session create its own `TCPConnector()` (it does so at client.py:364 when `connector is None`).
  3. If you must share a connector, create it lazily inside the running loop and reuse it only within that loop's lifetime.

Example fix

// before
connector = aiohttp.TCPConnector()  # built at import time, no loop
async def main():
    session = aiohttp.ClientSession(connector=connector)
// after
async def main():
    connector = aiohttp.TCPConnector()
    session = aiohttp.ClientSession(connector=connector)
Defensive patterns

Strategy: validation

Validate before calling

import asyncio

def same_loop(connector) -> bool:
    try:
        running = asyncio.get_running_loop()
    except RuntimeError:
        return False
    return connector._loop is running

Try / catch

try:
    session = aiohttp.ClientSession(connector=connector)
except RuntimeError as e:
    if 'same event loop' in str(e):
        # rebuild connector in the current loop
        connector = aiohttp.TCPConnector()
        session = aiohttp.ClientSession(connector=connector)
    else:
        raise

Prevention

When it happens

Trigger: Creating a `TCPConnector()` in one event loop/coroutine and then constructing `ClientSession(connector=that_connector)` inside a different loop (e.g., created the connector at import time outside any loop, or reused a connector across `asyncio.run()` calls).

Common situations: Module-level `connector = TCPConnector()` evaluated before any loop exists; reusing a connector after `asyncio.run()` tears down and starts a fresh loop; mixing threads with separate loops; test fixtures that build the connector in `setUp` but run the test in a new loop.

Related errors


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