{"record":{"id":"8740cb332bcf5ef8","repo":"python/cpython","slug":"empty-waiter-is-already-set","errorCode":null,"errorMessage":"Empty waiter is already set","messagePattern":"Empty waiter is already set","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"Lib/asyncio/selector_events.py","lineNumber":1223,"sourceCode":"        if self._buffer:\n            self._add_writer(self._sock_fd, self._write_ready)\n            self._maybe_pause_protocol()\n\n    def can_write_eof(self):\n        return True\n\n    def _call_connection_lost(self, exc):\n        try:\n            super()._call_connection_lost(exc)\n        finally:\n            self._write_ready = None\n            if self._empty_waiter is not None:\n                self._empty_waiter.set_exception(\n                    ConnectionError(\"Connection is closed by peer\"))\n\n    def _make_empty_waiter(self):\n        if self._empty_waiter is not None:\n            raise RuntimeError(\"Empty waiter is already set\")\n        self._empty_waiter = self._loop.create_future()\n        if not self._buffer:\n            self._empty_waiter.set_result(None)\n        return self._empty_waiter\n\n    def _reset_empty_waiter(self):\n        self._empty_waiter = None\n\n    def close(self):\n        self._read_ready_cb = None\n        super().close()\n\n\nclass _SelectorDatagramTransport(_SelectorTransport, transports.DatagramTransport):\n\n    _header_size = 8\n\n    def __init__(self, loop, sock, protocol, address=None,","sourceCodeStart":1205,"sourceCodeEnd":1241,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/asyncio/selector_events.py#L1205-L1241","documentation":"Raised by _SelectorSocketTransport._make_empty_waiter() as RuntimeError when an empty-waiter future already exists. The empty waiter is an internal future created at the start of a sendfile operation to wait for the write buffer to drain; a second one is only possible if sendfile is initiated while a previous sendfile is still pending, which the transport forbids.","triggerScenarios":"Two overlapping sendfile operations on the same transport: e.g. calling loop.sendfile(transport, f1) and, before it resolves, loop.sendfile(transport, f2); typically via concurrent tasks sharing one StreamWriter.","commonSituations":"A file-server handler processing pipelined requests concurrently over a single connection; retry logic that re-issues sendfile without awaiting the first; fire-and-forget task groups writing files to the same client connection.","solutions":["Serialize sendfile calls per transport: await each loop.sendfile() before starting the next.","Protect the transport with an asyncio.Lock or a per-connection worker queue.","Await/cancel outstanding sendfile tasks before starting a new one or closing the connection."],"exampleFix":"// before\nasyncio.create_task(loop.sendfile(transport, f1))\nawait loop.sendfile(transport, f2)  # RuntimeError: Empty waiter is already set\n\n// after\nasync with conn_lock:\n    await loop.sendfile(transport, f1)\n    await loop.sendfile(transport, f2)","handlingStrategy":"validation","validationCode":"file_lock = asyncio.Lock()\n\nasync def send_one_at_a_time(transport, paths):\n    for p in paths:\n        async with file_lock:  # prevents overlapping sendfiles\n            with open(p, 'rb') as f:\n                await loop.sendfile(transport, f)","typeGuard":null,"tryCatchPattern":"try:\n    await loop.sendfile(transport, f)\nexcept RuntimeError as e:\n    if 'Empty waiter' in str(e):\n        await prior_sendfile_task  # drain previous sendfile, then retry\n        await loop.sendfile(transport, f)\n    else:\n        raise","preventionTips":["Await every sendfile call to completion before initiating another on the same transport.","Pipelined file responses over one connection should go through a serialized sender task.","Cancel in-flight sendfile tasks before closing or reusing a connection."],"tags":["asyncio","networking","sendfile","internal-api","concurrency"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}