From 3424a51dd6a88aea324587232c7d9394f6678119 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 2 Oct 2026 18:49:03 +0200 Subject: [PATCH 1/2] gh-158585: Use PyBytesWriter in _io._Buffered.readline() Replace a list of bytes object with PyBytesWriter. --- Modules/_io/bufferedio.c | 126 +++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 59 deletions(-) diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 2cc3774e75c9dad..f6143e1bb930e8d 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -1205,109 +1205,117 @@ _io__Buffered_readinto1_impl(buffered *self, Py_buffer *buffer) static PyObject * _buffered_readline(buffered *self, Py_ssize_t limit) { - PyObject *res = NULL; - PyObject *chunks = NULL; - Py_ssize_t n; - const char *start, *s, *end; + _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(self); CHECK_CLOSED(self, "readline of closed file") /* First, try to find a line in the buffer. This can run unlocked because the calls to the C API are simple enough that they can't trigger any thread switch. */ - n = Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t); - if (limit >= 0 && n > limit) + Py_ssize_t n = Py_SAFE_DOWNCAST(READAHEAD(self), Py_off_t, Py_ssize_t); + if (limit >= 0 && n > limit) { n = limit; - start = self->buffer + self->pos; - s = memchr(start, '\n', n); + } + const char *start = self->buffer + self->pos; + const char *s = memchr(start, '\n', n); if (s != NULL) { - res = PyBytes_FromStringAndSize(start, s - start + 1); - if (res != NULL) - self->pos += s - start + 1; - goto end_unlocked; + n = s - start + 1; + PyObject *res = PyBytes_FromStringAndSize(start, n); + if (res == NULL) { + return NULL; + } + self->pos += n; + return res; } + if (n == limit) { - res = PyBytes_FromStringAndSize(start, n); - if (res != NULL) - self->pos += n; - goto end_unlocked; + PyObject *res = PyBytes_FromStringAndSize(start, n); + if (res == NULL) { + return NULL; + } + self->pos += n; + return res; } - if (!ENTER_BUFFERED(self)) - goto end_unlocked; + PyBytesWriter *writer = NULL; + int locked = 0; + if (!ENTER_BUFFERED(self)) { + goto error; + } + locked = 1; /* Now we try to get some more from the raw stream */ - chunks = PyList_New(0); - if (chunks == NULL) - goto end; + writer = PyBytesWriter_Create(0); + if (writer == NULL) { + goto error; + } + if (n > 0) { - res = PyBytes_FromStringAndSize(start, n); - if (res == NULL) - goto end; - if (PyList_Append(chunks, res) < 0) { - Py_CLEAR(res); - goto end; + if (PyBytesWriter_WriteBytes(writer, start, n) < 0) { + goto error; } - Py_CLEAR(res); self->pos += n; - if (limit >= 0) + if (limit >= 0) { limit -= n; + } } if (self->writable) { - PyObject *r = buffered_flush_and_rewind_unlocked(self); - if (r == NULL) - goto end; - Py_DECREF(r); + PyObject *res = buffered_flush_and_rewind_unlocked(self); + if (res == NULL) { + goto error; + } + Py_DECREF(res); } for (;;) { _bufferedreader_reset_buf(self); n = _bufferedreader_fill_buffer(self); - if (n == -1) - goto end; - if (n <= 0) + if (n == -1) { + goto error; + } + if (n <= 0) { break; - if (limit >= 0 && n > limit) + } + if (limit >= 0 && n > limit) { n = limit; + } start = self->buffer; - end = start + n; + const char *end = start + n; s = start; while (s < end) { if (*s++ == '\n') { - res = PyBytes_FromStringAndSize(start, s - start); - if (res == NULL) - goto end; + if (PyBytesWriter_WriteBytes(writer, start, s - start) < 0) { + goto error; + } self->pos = s - start; goto found; } } - res = PyBytes_FromStringAndSize(start, n); - if (res == NULL) - goto end; + + if (PyBytesWriter_WriteBytes(writer, start, n) < 0) { + goto error; + } if (n == limit) { self->pos = n; break; } - if (PyList_Append(chunks, res) < 0) { - Py_CLEAR(res); - goto end; - } - Py_CLEAR(res); - if (limit >= 0) + if (limit >= 0) { limit -= n; + } } + found: - if (res != NULL && PyList_Append(chunks, res) < 0) { - Py_CLEAR(res); - goto end; + if (locked) { + LEAVE_BUFFERED(self) } - Py_XSETREF(res, PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), chunks)); + return PyBytesWriter_Finish(writer); -end: - LEAVE_BUFFERED(self) -end_unlocked: - Py_XDECREF(chunks); - return res; +error: + PyBytesWriter_Discard(writer); + if (locked) { + LEAVE_BUFFERED(self) + } + return NULL; } /*[clinic input] From e4e4a7480fa8a12d80dafe2a9486ea0e795a1dbc Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sat, 3 Oct 2026 23:39:56 +0200 Subject: [PATCH 2/2] At found label, the buffer is always locked --- Modules/_io/bufferedio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index f6143e1bb930e8d..d6318af1263d9c6 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -1305,9 +1305,7 @@ _buffered_readline(buffered *self, Py_ssize_t limit) } found: - if (locked) { - LEAVE_BUFFERED(self) - } + LEAVE_BUFFERED(self) return PyBytesWriter_Finish(writer); error: