python/cpython · error · RuntimeError
Cannot call write() after write_eof()
Error message
Cannot call write() after write_eof()
What it means
Raised by _SelectorSocketTransport.write() as RuntimeError when write() is called after write_eof() has already been invoked on the same transport. write_eof() half-closes the socket (shutdown(SHUT_WR)) marking that no more data will be sent; any subsequent write attempt violates that contract and is rejected instead of silently dropped.
Source
Thrown at Lib/asyncio/selector_events.py:1066
except BaseException as exc:
self._fatal_error(
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):View on GitHub (pinned to bc6749cc3b)
Solutions
- Reorder logic so write_eof() is the very last operation on the transport, after all writes and drain() calls complete.
- Track EOF state in your protocol/handler and skip or queue-then-drop writes once EOF was sent.
- Cancel producer tasks before calling write_eof() so no concurrent write can race the half-close.
- Check transport.is_closing() and your own eof flag before writing from callbacks.
Example fix
// before writer.write_eof() writer.write(b"late data\n") # RuntimeError // after writer.write(b"early data\n") await writer.drain() writer.write_eof() # always last
Defensive patterns
Strategy: validation
Validate before calling
eof_sent = False
def safe_write(transport, data):
if eof_sent or transport.is_closing():
return False # drop or raise your own error
transport.write(data)
return True
def close_write_side(transport):
global eof_sent
eof_sent = True
transport.write_eof() Try / catch
try:
transport.write(data)
except RuntimeError as e:
if 'write_eof' in str(e):
log.debug('dropping write after EOF: %r', data[:64])
else:
raise Prevention
- Treat write_eof() as the terminal write operation; nothing may follow it.
- Await producer tasks before half-closing so writes cannot race EOF.
- Track EOF in your protocol state and guard every write site.
When it happens
Trigger: Calling transport.write_eof() (or StreamWriter.write_eof()) and then transport.write(data) on the same connection; common when EOF is sent inside a finally block that runs before queued writes, or when a handler signals end-of-request then appends trailer data.
Common situations: HTTP/line protocols where the handler writes EOF early to signal end of request then tries to write a response; cleanup code that calls write_eof() on error paths while a background writer task is still producing data; bidirectional protocols misordering shutdown.
Related errors
- Cannot call writelines() after write_eof()
- data argument must be a bytes, bytearray, or memoryview obje
- unable to write; sendfile is in progress
- Failed to fetch glossary.json
- d3-flame-graph library failed to load
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/f2313c415d005250.
Report an issue: GitHub.