python/cpython · error · RuntimeError

unable to write; sendfile is in progress

Error message

unable to write; sendfile is in progress

What it means

Raised by _SelectorSocketTransport.write() as RuntimeError when a sendfile operation is in progress on the transport. During sendfile the transport sets _empty_waiter (an internal future awaiting an empty write buffer); concurrent write() calls are forbidden because they would corrupt the buffer state the sendfile completion logic depends on.

Source

Thrown at Lib/asyncio/selector_events.py:1068

                exc, 'Fatal error: protocol.eof_received() call failed.')
            return

        if keep_open:
            # We're keeping the connection open so the
            # protocol can write more, but we still can't
            # receive more, so remove the reader callback.
            self._loop._remove_reader(self._sock_fd)
        else:
            self.close()

    def write(self, data):
        if not isinstance(data, (bytes, bytearray, memoryview)):
            raise TypeError(f'data argument must be a bytes, bytearray, or memoryview '
                            f'object, not {type(data).__name__!r}')
        if self._eof:
            raise RuntimeError('Cannot call write() after write_eof()')
        if self._empty_waiter is not None:
            raise RuntimeError('unable to write; sendfile is in progress')
        if not 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

        if not self._buffer:
            # Optimization: try to send now.
            try:
                n = self._sock.send(data)
            except (BlockingIOError, InterruptedError):
                pass
            except (SystemExit, KeyboardInterrupt):
                raise
            except BaseException as exc:

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Await the sendfile() call to completion before issuing any write() on that transport.
  2. Serialize access to the transport with an asyncio.Lock around sendfile/write sequences.
  3. If interleaving is required, avoid sendfile and stream the file with read()+write() instead (setbufsize / fallback mode).
  4. Cancel or pause background writer tasks for the duration of the sendfile.

Example fix

// before
sendfile_task = asyncio.create_task(loop.sendfile(transport, f))
transport.write(b"next request?\n")  # RuntimeError

// after
await loop.sendfile(transport, f)
transport.write(b"next request?\n")
Defensive patterns

Strategy: validation

Validate before calling

conn_lock = asyncio.Lock()

async def send_file_then_write(transport, path, extra):
    async with conn_lock:
        with open(path, 'rb') as f:
            await loop.sendfile(transport, f)
        transport.write(extra)  # only after sendfile resolves

Try / catch

try:
    transport.write(data)
except RuntimeError as e:
    if 'sendfile is in progress' in str(e):
        await sendfile_task  # wait, then retry once
        transport.write(data)
    else:
        raise

Prevention

When it happens

Trigger: Calling transport.write(data) (directly or via StreamWriter) while a loop.sendfile(transport, file) / StreamWriter.sendfile() call on the same transport is still awaiting.

Common situations: A server handler serving a file with sendfile while a heartbeat/keepalive task writes to the same connection; concurrent tasks sharing one StreamWriter where one sends files; protocols that interleave control frames with large file transfers.

Related errors


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