python/cpython · error · RuntimeError

unable to writelines; sendfile is in progress

Error message

unable to writelines; sendfile is in progress

What it means

Raised by _SelectorSocketTransport.writelines() as RuntimeError when the internal _empty_waiter future is set, i.e. a sendfile operation is currently in progress on the transport. writelines() would append to the write buffer that sendfile is waiting to drain, so asyncio rejects the call to keep the buffer state consistent.

Source

Thrown at Lib/asyncio/selector_events.py:1190

                if self._empty_waiter is not None:
                    self._empty_waiter.set_result(None)
                if self._closing:
                    self._call_connection_lost(None)
                elif self._eof:
                    self._sock.shutdown(socket.SHUT_WR)

    def write_eof(self):
        if self._closing or self._eof:
            return
        self._eof = True
        if not self._buffer:
            self._sock.shutdown(socket.SHUT_WR)

    def writelines(self, list_of_data):
        if self._eof:
            raise RuntimeError('Cannot call writelines() after write_eof()')
        if self._empty_waiter is not None:
            raise RuntimeError('unable to writelines; sendfile is in progress')
        if not list_of_data:
            return

        if self._conn_lost:
            if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
                logger.warning('socket.send() raised exception.')
            self._conn_lost += 1
            return

        for data in list_of_data:
            self._buffer.append(memoryview(data))
            self._buffer_size += len(data)
        self._write_ready()
        # If the entire buffer couldn't be written, register a write handler
        if self._buffer:
            self._add_writer(self._sock_fd, self._write_ready)
            self._maybe_pause_protocol()

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Await the sendfile() completion before calling writelines() on that transport.
  2. Wrap transport access in an asyncio.Lock so sendfile and buffered writes cannot interleave.
  3. Stop background writers for the connection before initiating sendfile.

Example fix

// before
asyncio.create_task(loop.sendfile(transport, f))
transport.writelines(chunks)  # RuntimeError

// after
await loop.sendfile(transport, f)
transport.writelines(chunks)
Defensive patterns

Strategy: validation

Validate before calling

async def batched_send(transport, chunks, path=None):
    if path:
        with open(path, 'rb') as f:
            await loop.sendfile(transport, f)  # fully awaited first
    transport.writelines(chunks)  # safe: no sendfile pending

Try / catch

try:
    transport.writelines(chunks)
except RuntimeError as e:
    if 'sendfile is in progress' in str(e):
        await asyncio.shield(sendfile_task)
        transport.writelines(chunks)
    else:
        raise

Prevention

When it happens

Trigger: Calling transport.writelines(list_of_data) while loop.sendfile(transport, file) (or the transport sendfile path) has not yet completed on the same transport.

Common situations: Batch-flushing queued messages via writelines() from a background task while the request handler serves a file with sendfile on the same connection; log/heartbeat writers racing large file transfers.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/0c70bfb1ba27c59b. Report an issue: GitHub.