{"record":{"id":"d644b2edf235b1c9","repo":"python/cpython","slug":"cannot-call-writelines-after-write-eof","errorCode":null,"errorMessage":"Cannot call writelines() after write_eof()","messagePattern":"Cannot call writelines\\(\\) after write_eof\\(\\)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"Lib/asyncio/selector_events.py","lineNumber":1188,"sourceCode":"            if not self._buffer:\n                self._loop._remove_writer(self._sock_fd)\n                if self._empty_waiter is not None:\n                    self._empty_waiter.set_result(None)\n                if self._closing:\n                    self._call_connection_lost(None)\n                elif self._eof:\n                    self._sock.shutdown(socket.SHUT_WR)\n\n    def write_eof(self):\n        if self._closing or self._eof:\n            return\n        self._eof = True\n        if not self._buffer:\n            self._sock.shutdown(socket.SHUT_WR)\n\n    def writelines(self, list_of_data):\n        if self._eof:\n            raise RuntimeError('Cannot call writelines() after write_eof()')\n        if self._empty_waiter is not None:\n            raise RuntimeError('unable to writelines; sendfile is in progress')\n        if not list_of_data:\n            return\n\n        if self._conn_lost:\n            if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:\n                logger.warning('socket.send() raised exception.')\n            self._conn_lost += 1\n            return\n\n        for data in list_of_data:\n            self._buffer.append(memoryview(data))\n            self._buffer_size += len(data)\n        self._write_ready()\n        # If the entire buffer couldn't be written, register a write handler\n        if self._buffer:\n            self._add_writer(self._sock_fd, self._write_ready)","sourceCodeStart":1170,"sourceCodeEnd":1206,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/asyncio/selector_events.py#L1170-L1206","documentation":"Raised by _SelectorSocketTransport.writelines() as RuntimeError when called after write_eof() has marked the transport as half-closed. writelines() is the batched variant of write() and obeys the same lifecycle rule: once the write side is shut down no further outgoing data is accepted.","triggerScenarios":"Calling transport.writelines([chunk1, chunk2]) or StreamWriter.writelines() after transport.write_eof() was already invoked on the same connection.","commonSituations":"Response-building code that calls write_eof() after finishing headers, then appends body chunks via writelines; shared handlers where one path half-closes early; refactoring write() loops into writelines() without revisiting the EOF ordering.","solutions":["Move writelines() before write_eof() so EOF is strictly the final transport operation.","Guard writes with an `if transport.is_closing() or eof_sent: return` check in your protocol wrapper.","Ensure producer tasks are done (await them) before half-closing the connection."],"exampleFix":"// before\ntransport.write_eof()\ntransport.writelines([b\"a\\n\", b\"b\\n\"])  # RuntimeError\n\n// after\ntransport.writelines([b\"a\\n\", b\"b\\n\"])\ntransport.write_eof()","handlingStrategy":"validation","validationCode":"class WriteOnceEofTransport:\n    def __init__(self, transport):\n        self._t = transport\n        self._eof = False\n    def writelines(self, chunks):\n        if self._eof or self._t.is_closing():\n            return\n        self._t.writelines(chunks)\n    def write_eof(self):\n        self._eof = True\n        self._t.write_eof()","typeGuard":null,"tryCatchPattern":"try:\n    transport.writelines(chunks)\nexcept RuntimeError as e:\n    if 'after write_eof' in str(e):\n        log.warning('dropped %d chunks after EOF', len(chunks))\n    else:\n        raise","preventionTips":["Order handlers so all body/batch writes precede the write_eof() call.","Await drain() after the last writelines before half-closing.","Unit-test protocol handlers for write-after-EOF orderings."],"tags":["asyncio","networking","transport","eof","writelines"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}