Skip to main content

Modules/_io/bufferedio.c

cpython 3.14 @ ab2d84fe1023/Modules/_io/bufferedio.c

bufferedio.c implements all four buffered I/O classes defined by PEP 3116: BufferedReader, BufferedWriter, BufferedRandom, and BufferedRWPair. The first three share a single C struct (buffered) and most of their method implementations, with readable / writable flags routing to the appropriate code paths at runtime. BufferedRWPair is a thin wrapper that holds a BufferedReader and a BufferedWriter side by side.

The central design choice is the single shared buffer. BufferedRandom, which must support both reads and writes on a seekable stream, reuses the exact same buffer memory for both directions. The read_end and write_end fields delimit which portion of the buffer holds valid read-ahead data versus pending write data. A reentrant lock (lock, owner) protects all buffer mutations against concurrent access from threads calling the same stream object.

Three internal helpers carry most of the logic: _bufferedreader_raw_read (wraps the underlying raw readinto), _bufferedreader_fill_buffer (fills the read-ahead region), and _bufferedwriter_flush_unlocked (drains the write region to the raw stream).

Map​

LinesSymbolRolegopy
1-220bufferediobase_doc, _bufferediobase_readinto_generic, _io__BufferedIOBase_*Abstract base _BufferedIOBase methods: read, read1, readinto, readinto1, write, detach.module/io/
222-400buffered struct, _enter_buffered_busy, ENTER_BUFFERED, LEAVE_BUFFEREDShared struct definition and reentrant-lock macros used by every method.module/io/
400-900buffered_flush_and_rewind_unlocked, _buffered_raw_seek, _buffered_raw_tell, buffered_tell, buffered_seek, buffered_close, buffered_detachSeek/tell/close/detach shared by all three classes.module/io/
900-1560buffered_readinto_generic, buffered_peek_impl, _io_BufferedReader_read1_impl, _io_BufferedReader_read_impl, _io_BufferedReader_readline_implBufferedReader read methods: peek, read1, read, readline.module/io/
1560-1912_io_BufferedReader___init___impl, _bufferedreader_raw_read, _bufferedreader_fill_buffer, _bufferedreader_read_all, _bufferedreader_read_fast, _bufferedreader_read_genericBufferedReader construction and the read-ahead fill loop.module/io/
1912-2225_io_BufferedWriter___init___impl, _bufferedwriter_raw_write, _bufferedwriter_flush_unlocked, _io_BufferedWriter_write_implBufferedWriter construction, raw write wrapper, flush drain loop, and write method.module/io/
2223-2455rwpair, _io_BufferedRWPair___init___impl, bufferedrwpair_*BufferedRWPair: wraps a BufferedReader and a BufferedWriter with forwarding methods.module/io/
2454-2773_io_BufferedRandom___init___impl, type slot tables, PyType_Spec definitions for all four classesBufferedRandom construction (sets both readable and writable) and all four type specs.module/io/

Reading​

The buffered struct (lines 222 to 265)​

cpython 3.14 @ ab2d84fe1023/Modules/_io/bufferedio.c#L222-265

All three concrete classes allocate a buffered instance. The key fields are:

typedef struct {
PyObject_HEAD

PyObject *raw;
int ok; /* Initialized? */
int readable;
int writable;

Py_off_t abs_pos; /* absolute position in raw stream, or -1 */

char *buffer; /* static buffer of size buffer_size */
Py_off_t pos; /* current logical position in buffer */
Py_off_t raw_pos; /* where the raw stream thinks it is */

Py_off_t read_end; /* one past last valid read-ahead byte, or -1 */
Py_off_t write_pos; /* start of pending write data */
Py_off_t write_end; /* one past last pending write byte, or -1 */

PyThread_type_lock lock;
volatile unsigned long owner;

Py_ssize_t buffer_size;
Py_ssize_t buffer_mask;
} buffered;

read_end and write_end are sentinel values: -1 means the corresponding region is not valid. VALID_READ_BUFFER(self) and VALID_WRITE_BUFFER(self) check these sentinels. raw_pos tracks where the OS file offset currently sits, allowing seeks to be elided when the desired position is already in the buffer.

_bufferedreader_fill_buffer (lines 1659 to 1673)​

cpython 3.14 @ ab2d84fe1023/Modules/_io/bufferedio.c#L1659-1673

_bufferedreader_fill_buffer is the innermost fill routine. It appends directly after the existing valid read-ahead data (if any), or resets to position 0 if the buffer is empty:

static Py_ssize_t
_bufferedreader_fill_buffer(buffered *self)
{
Py_ssize_t start, len, n;
if (VALID_READ_BUFFER(self))
start = Py_SAFE_DOWNCAST(self->read_end, Py_off_t, Py_ssize_t);
else
start = 0;
len = self->buffer_size - start;
n = _bufferedreader_raw_read(self, self->buffer + start, len);
if (n <= 0)
return n;
self->read_end = start + n;
self->raw_pos = start + n;
return n;
}

The raw read calls the underlying stream's readinto through a memoryview wrapper. Returns -2 when the raw stream would block (non-blocking mode), 0 for EOF, and a positive byte count on success.

_bufferedwriter_flush_unlocked write path (lines 2014 to 2067)​

cpython 3.14 @ ab2d84fe1023/Modules/_io/bufferedio.c#L2014-2067

Flushing a BufferedWriter first rewinds the raw stream to align with the start of the write buffer (write_pos), then drains the pending region in a loop to handle partial writes from the raw layer:

static PyObject *
_bufferedwriter_flush_unlocked(buffered *self)
{
Py_off_t n, rewind;

if (!VALID_WRITE_BUFFER(self) || self->write_pos == self->write_end)
goto end;

/* First, rewind */
rewind = RAW_OFFSET(self) + (self->pos - self->write_pos);
if (rewind != 0) {
n = _buffered_raw_seek(self, -rewind, 1);
if (n < 0) {
goto error;
}
self->raw_pos -= rewind;
}

while (self->write_pos < self->write_end) {
n = _bufferedwriter_raw_write(self,
self->buffer + self->write_pos,
Py_SAFE_DOWNCAST(self->write_end - self->write_pos,
Py_off_t, Py_ssize_t));
if (n == -1) {
goto error;
}
else if (n == -2) {
_set_BlockingIOError("write could not complete without blocking", 0);
goto error;
}
self->write_pos += n;
self->raw_pos = self->write_pos;
if (PyErr_CheckSignals() < 0)
goto error;
}

end:
_bufferedwriter_reset_buf(self);
Py_RETURN_NONE;

error:
return NULL;
}

The PyErr_CheckSignals call inside the loop lets signal handlers run between partial writes, which matters for streams that can block (such as pipes).

BufferedWriter.write fast path (lines 2077 to 2115)​

cpython 3.14 @ ab2d84fe1023/Modules/_io/bufferedio.c#L2077-2115

The write method has a fast path for data that fits entirely within the remaining buffer space without requiring a flush. It calls memcpy directly into the buffer and updates write_pos / write_end:

avail = Py_SAFE_DOWNCAST(self->buffer_size - self->pos, Py_off_t, Py_ssize_t);
if (buffer->len <= avail && buffer->len < self->buffer_size) {
memcpy(self->buffer + self->pos, buffer->buf, buffer->len);
if (!VALID_WRITE_BUFFER(self) || self->write_pos > self->pos) {
self->write_pos = self->pos;
}
ADJUST_POSITION(self, self->pos + buffer->len);
if (self->pos > self->write_end)
self->write_end = self->pos;
written = buffer->len;
goto end;
}

If the data is larger than available space (or larger than the whole buffer), the slow path calls _bufferedwriter_flush_unlocked first, then issues the remainder directly to the raw stream, bypassing the buffer entirely.

gopy mirror​

module/io/ (pending). The Go port represents buffered as a single struct with readable / writable booleans, matching CPython's shared-struct approach. The buffer field becomes a []byte slice with pos, rawPos, readEnd, writePos, and writeEnd fields carrying the same semantics. The reentrant lock maps to a sync.Mutex with an owner goroutine ID checked on entry. _bufferedreader_fill_buffer and _bufferedwriter_flush_unlocked are ported one-to-one with the same loop structure and sentinel handling.