Skip to main content

Modules/_io/bufferedio.c

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

Modules/_io/bufferedio.c implements the buffered I/O layer of CPython's _io module. It provides BufferedReader, BufferedWriter, BufferedRandom, and BufferedRWPair, each wrapping a raw FileIO-like object. BufferedReader maintains a read-ahead buffer; BufferedWriter coalesces small writes into a larger block before calling raw.write(); BufferedRandom combines both for seekable files.

Map

LinesSymbolRole
1-200buffered structBuffer, pos, raw pointer, lock, and readahead fields
201-600BufferedReader init and _bufferedreader_read_genericRead path with buffer refill
601-900_bufferedreader_readintoReadinto bypassing buffer when request exceeds buffer size
901-1200BufferedWriter init and _bufferedwriter_flush_unlockedWrite path and flush
1201-1500buffered_seek, buffered_tellSeek adjusting logical position and discarding stale buffer
1501-1800BufferedRandomCombines reader and writer, switching modes on seek
1801-2200BufferedRWPairWrapper pairing two separate reader and writer objects
2201-2800type objects and PyInitPyBufferedReader_Type, PyBufferedWriter_Type

Reading

Read-ahead buffer and buffer refill

BufferedReader maintains a contiguous buffer of buffer_size bytes. read(n) first copies from the buffer, then calls raw.read() to refill when needed. The logical read position is raw_pos - (raw_buf_end - pos), tracking how far ahead the raw layer has read.

// CPython: Modules/_io/bufferedio.c:300 _bufferedreader_fill_buffer
static Py_ssize_t
_bufferedreader_fill_buffer(buffered *self)
{
Py_ssize_t start, len, n;
if (VALID_READ_BUFFER(self))
start = self->read_end;
else
start = 0;
len = self->buffer_size - start;
n = self->raw->ob_type->tp_as_buffer... /* raw.readinto(view) */
...
self->read_end = start + n;
return n;
}

_bufferedwriter_flush_unlocked: write coalescing

BufferedWriter accumulates writes in self->buffer. On flush (or when the buffer is full), it calls raw.write() in a loop until all buffered data is consumed. The loop handles short writes by advancing the buffer position.

// CPython: Modules/_io/bufferedio.c:990 _bufferedwriter_flush_unlocked
static PyObject *
_bufferedwriter_flush_unlocked(buffered *self)
{
Py_ssize_t written = 0;
Py_buffer data;
...
while (self->write_pos < self->write_end) {
n = self->raw_write(self->raw, ...); /* raw.write() */
...
self->write_pos += n;
written += n;
}
return PyLong_FromSsize_t(written);
}

buffered_seek: discarding stale read-ahead

When seek() moves the file position, any buffered read-ahead data is invalid. The implementation resets read_pos and read_end to zero and calls raw.seek() to set the underlying file descriptor position. For BufferedRandom, a flush of the write buffer precedes the seek.

// CPython: Modules/_io/bufferedio.c:1520 buffered_seek_impl
static PyObject *
buffered_seek_impl(buffered *self, PyObject *targetobj, int whence)
{
...
if (self->readable)
_bufferedreader_reset_buf(self);
n = PyObject_CallMethodObjArgs(self->raw, _PyIO_str_seek, ...);
...
self->pos = n;
return PyLong_FromSsize_t(n);
}

gopy notes

Not yet ported. The Go equivalent is bufio.Reader and bufio.Writer. A module/io/ port would wrap these and expose BufferedReader and BufferedWriter as gopy types. The locking model (self->lock in CPython) maps to sync.Mutex protecting the buffer fields.

CPython 3.14 changes

3.14 improved BufferedWriter.write() to avoid an extra copy when the incoming buffer is larger than the internal buffer size. BufferedReader gained read1() performance improvements for the common single-chunk case.