{"id":"4787f5a133a8711f","repo":"aio-libs/aiohttp","slug":"connection-closed-4787f5","errorCode":null,"errorMessage":"Connection closed.","messagePattern":"Connection closed\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"aiohttp/streams.py","lineNumber":351,"sourceCode":"            return\n\n        self._http_chunk_splits.append(self.total_bytes)\n\n        # If we get too many small chunks before self._high_water is reached, then any\n        # .read() call becomes computationally expensive, and could block the event loop\n        # for too long, hence an additional self._high_water_chunks here.\n        if len(self._http_chunk_splits) > self._high_water_chunks:\n            self._protocol.pause_reading()\n\n        # wake up readchunk when end of http chunk received\n        waiter = self._waiter\n        if waiter is not None:\n            self._waiter = None\n            set_result(waiter, None)\n\n    async def _wait(self, func_name: str) -> None:\n        if not self._protocol.connected:\n            raise RuntimeError(\"Connection closed.\")\n\n        # StreamReader uses a future to link the protocol feed_data() method\n        # to a read coroutine. Running two read coroutines at the same time\n        # would have an unexpected behaviour. It would not possible to know\n        # which coroutine would get the next data.\n        if self._waiter is not None:\n            raise RuntimeError(\n                \"%s() called while another coroutine is \"\n                \"already waiting for incoming data\" % func_name\n            )\n\n        waiter = self._waiter = self._loop.create_future()\n        try:\n            with self._timer:\n                await waiter\n        finally:\n            self._waiter = None\n","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/streams.py#L333-L369","documentation":"Raised by StreamReader._wait when self._protocol.connected is False — i.e. a read coroutine (read/readany/readline/readchunk) is invoked after the transport has already closed. aiohttp refuses to await a future that can never be resolved because there is no live connection to feed it. The proper signal for end-of-stream is EOF (at_eof()), not a RuntimeError, so this is a genuine misuse.","triggerScenarios":"Calling await request.content.read(...) after request.release()/response close; reading a client response body after the session or connection was closed; using the same StreamReader from two lifecycles; reading after the server dropped the connection without sending EOF.","commonSituations":"Forgetting to keep the ClientSession / response context manager open while background task reads; calling read() in a finally block after the connection broke; reusing a request object across requests; webhook handler reading body after returning a response that closed the connection.","solutions":["Read the body inside the `async with session.get(...) as r:` block before it exits.","Check `await request.can_read_body()` / `request.body_exists` before reading.","Catch RuntimeError and treat as end-of-stream if your flow tolerates abrupt close.","Ensure no background task outlives the response context manager."],"exampleFix":"// before\nasync with session.get(url) as r:\n    data = r  # captured\nreturn await data.content.read()  # connection already closed\n// after\nasync with session.get(url) as r:\n    return await r.read()","handlingStrategy":"validation","validationCode":"# check liveness before reading:\nif not request.content.is_eof() and not request.content._protocol.connected:\n    raise ClientConnectionError('connection already closed')\nif request.content.at_eof():\n    return b''\nreturn await request.content.read()","typeGuard":null,"tryCatchPattern":"try:\n    chunk = await stream.read(n)\nexcept RuntimeError as e:\n    if 'Connection closed' in str(e):\n        return b''  # treat as EOF\n    raise","preventionTips":["Always read the body inside the response context manager.","Check request.content.at_eof() / can_read_body() before reading.","Do not retain references to request/response across lifecycles."],"tags":["streams","connection","lifecycle"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}