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
- If you truly want force_close (no pooling), drop keepalive_timeout entirely.
- If you want pooling with a custom lifetime, set force_close=False (default) and keep keepalive_timeout.
- Centralize connector construction in one helper so the two fields cannot drift apart.
- 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
- Centralize connector construction in one helper so force_close and keepalive_timeout stay consistent.
- Treat force_close=True as 'no pooling at all' and leave keepalive_timeout unset.
- Add a config validator in front of user-supplied connector settings.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- either both host and port or none of them are allowed
- ssl should be SSLContext, Fingerprint, or bool, got
- aiohttp only supports http(s) proxies
- base_url must have a trailing '/'
- Cannot connect to unix socket
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)