{"id":"3f6cda4a5e28a431","repo":"aio-libs/aiohttp","slug":"connection-closed","errorCode":null,"errorMessage":"Connection closed","messagePattern":"Connection closed","errorType":"exception","errorClass":"ClientConnectionError","httpStatus":null,"severity":"error","filePath":"aiohttp/client_reqrep.py","lineNumber":706,"sourceCode":"\n    async def _on_chunk_response_received(self, chunk: bytes) -> None:\n        try:\n            for trace in self._traces:\n                await trace.send_response_chunk_received(self.method, self.url, chunk)\n        except BaseException:\n            self.close()\n            raise\n\n    async def read(self) -> bytes:\n        \"\"\"Read response payload.\"\"\"\n        if self._body is None:\n            try:\n                self._body = await self.content.read()\n            except BaseException:\n                self.close()\n                raise\n        elif self._released:  # Response explicitly released\n            raise ClientConnectionError(\"Connection closed\")\n\n        protocol = self._connection and self._connection.protocol\n        if protocol is None or not protocol.upgraded:\n            await self._wait_released()  # Underlying connection released\n        return self._body\n\n    def get_encoding(self) -> str:\n        ctype = self.headers.get(hdrs.CONTENT_TYPE, \"\").lower()\n        mimetype = parse_mimetype(ctype)\n\n        encoding = mimetype.parameters.get(\"charset\")\n        if encoding:\n            with contextlib.suppress(LookupError, ValueError):\n                return codecs.lookup(encoding).name\n\n        if mimetype.type == \"application\" and (\n            mimetype.subtype == \"json\" or mimetype.subtype == \"rdap\"\n        ):","sourceCodeStart":688,"sourceCodeEnd":724,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/client_reqrep.py#L688-L724","documentation":"Raised as ClientConnectionError in ClientResponse.read() when the response body has already been released (self._released is True) but read() is called again. After release(), the underlying connection is returned to the pool and the payload stream is gone, so there is nothing left to read.","triggerScenarios":"Fires at line 705-706 when self._body is not None (so the first branch is skipped) but self._released flag is set. Happens when calling response.read()/text()/json() a second time after response.release() was invoked, or after the async context manager exited and released the response.","commonSituations":"Calling await response.read() after the `async with session.get(...)` block has already exited; mixing manual release() with later reads; retry logic that re-reads a released response.","solutions":["Cache the result of the first read(): body = await resp.read() and reuse `body`.","Avoid calling release() before you finish reading; let the context manager handle it.","Keep all reads inside the `async with` block.","If you need the body later, store it in a variable before exiting the context."],"exampleFix":"# before\nasync with session.get(url) as resp:\n    pass\ndata = await resp.read()  # response already released\n# after\nasync with session.get(url) as resp:\n    data = await resp.read()\n# use `data` afterwards","handlingStrategy":"try-catch","validationCode":"if resp._released:\n    raise RuntimeError('response already released; use cached body')","typeGuard":null,"tryCatchPattern":"from aiohttp import ClientConnectionError\ntry:\n    body = await resp.read()\nexcept ClientConnectionError as e:\n    if 'Connection closed' in str(e):\n        # response was released; re-fetch or use cached body","preventionTips":["Read the body once inside the async with block and cache the bytes.","Never call release() before finishing reads.","Treat the response as single-use; design retry code to re-issue the request."],"tags":["http-client","lifecycle","connection-pool","response-handling"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}