aio-libs/aiohttp · error · ValueError

keepalive_timeout cannot be set if force_close is True

Error message

keepalive_timeout cannot be set if force_close is True

What it means

Raised by BaseConnector.__init__ when force_close=True is combined with an explicit keepalive_timeout (anything other than None or sentinel). force_close means connections are discarded after each request, so a keep-alive timeout is meaningless and almost always indicates a configuration mistake. The constructor rejects the contradiction up front instead of silently ignoring one of the values.

Solutions

  1. If you truly want force_close (no pooling), drop keepalive_timeout entirely.
  2. If you want pooling with a custom lifetime, set force_close=False (default) and keep keepalive_timeout.
  3. Centralize connector construction in one helper so the two fields cannot drift apart.
  4. Validate the config dict before passing it to TCPConnector.

Example fix

# before
connector = aiohttp.TCPConnector(force_close=True, keepalive_timeout=30)
# after (pick one based on intent)
connector = aiohttp.TCPConnector(force_close=True)
# or
connector = aiohttp.TCPConnector(force_close=False, keepalive_timeout=30)
Defensive patterns

Strategy: validation

Validate before calling

def build_connector(force_close=False, keepalive_timeout=None):
    if force_close and keepalive_timeout is not None:
        raise ValueError('keepalive_timeout is incompatible with force_close')
    return aiohttp.TCPConnector(force_close=force_close, keepalive_timeout=keepalive_timeout)

Type guard

null

Try / catch

try:
    connector = aiohttp.TCPConnector(force_close=force_close, keepalive_timeout=ka)
except ValueError as exc:
    if 'force_close' in str(exc):
        ka = None if force_close else ka
        connector = aiohttp.TCPConnector(force_close=force_close, keepalive_timeout=ka)
    else:
        raise

Prevention

When it happens

Trigger: TCPConnector(force_close=True, keepalive_timeout=30); copying values from a shared config dict that sets both; toggling force_close at runtime without clearing the timeout; misreading docs and assuming the timeout governs something else.

Common situations: Reusing a connector-config helper across services where one needs force_close and another needs keepalive; CI config copied from a template that hard-codes both; refactoring that left a stale field.

Understand the failure class

Related errors


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

Appendix: source

Thrown at aiohttp/connector.py:339

    # abort transport after 2 seconds (cleanup broken connections)
    _cleanup_closed_period = 2.0

    allowed_protocol_schema_set = HIGH_LEVEL_SCHEMA_SET

    def __init__(
        self,
        *,
        keepalive_timeout: _SENTINEL | None | float = sentinel,
        force_close: bool = False,
        limit: int = 100,
        limit_per_host: int = 0,
        enable_cleanup_closed: bool = False,
        timeout_ceil_threshold: float = 5,
    ) -> None:
        if force_close:
            if keepalive_timeout is not None and keepalive_timeout is not sentinel:
                raise ValueError(
                    "keepalive_timeout cannot be set if force_close is True"
                )
        else:
            if keepalive_timeout is sentinel:
                keepalive_timeout = 15.0

        self._timeout_ceil_threshold = timeout_ceil_threshold

        loop = asyncio.get_running_loop()

        self._closed = False
        if loop.get_debug():
            self._source_traceback = traceback.extract_stack(sys._getframe(1))

        # Connection pool of reusable connections.
        # We use a deque to store connections because it has O(1) popleft()
        # and O(1) append() operations to implement a FIFO queue.
        self._conns: defaultdict[

View on GitHub (pinned to d041d4d0fd)