[3.15] gh-151814: Fix unbounded memory growth from repeated empty writes to io.TextIOWrapper (GH-151817)

(cherry picked from commit c61307222e)

Co-authored-by: Stan Ulbrych <stan@python.org>
This commit is contained in:
Miss Islington (bot) 2026-06-24 14:14:01 +02:00 committed by GitHub
parent bc52a01e71
commit 6bad84d64c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 28 additions and 20 deletions

View file

@ -0,0 +1,2 @@
Fix unbounded memory growth in :class:`io.TextIOWrapper` when repeatedly
writing an empty string.

View file

@ -1755,32 +1755,38 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
}
}
if (self->pending_bytes == NULL) {
assert(self->pending_bytes_count == 0);
self->pending_bytes = b;
}
else if (!PyList_CheckExact(self->pending_bytes)) {
PyObject *list = PyList_New(2);
if (list == NULL) {
Py_DECREF(b);
return NULL;
if (bytes_len > 0) {
if (self->pending_bytes == NULL) {
assert(self->pending_bytes_count == 0);
self->pending_bytes = b;
}
// Since Python 3.12, allocating GC object won't trigger GC and release
// GIL. See https://github.com/python/cpython/issues/97922
assert(!PyList_CheckExact(self->pending_bytes));
PyList_SET_ITEM(list, 0, self->pending_bytes);
PyList_SET_ITEM(list, 1, b);
self->pending_bytes = list;
else if (!PyList_CheckExact(self->pending_bytes)) {
PyObject *list = PyList_New(2);
if (list == NULL) {
Py_DECREF(b);
return NULL;
}
// Since Python 3.12, allocating GC object won't trigger GC and release
// GIL. See https://github.com/python/cpython/issues/97922
assert(!PyList_CheckExact(self->pending_bytes));
PyList_SET_ITEM(list, 0, self->pending_bytes);
PyList_SET_ITEM(list, 1, b);
self->pending_bytes = list;
}
else {
if (PyList_Append(self->pending_bytes, b) < 0) {
Py_DECREF(b);
return NULL;
}
Py_DECREF(b);
}
self->pending_bytes_count += bytes_len;
}
else {
if (PyList_Append(self->pending_bytes, b) < 0) {
Py_DECREF(b);
return NULL;
}
Py_DECREF(b);
}
self->pending_bytes_count += bytes_len;
if (self->pending_bytes_count >= self->chunk_size || needflush ||
text_needflush) {
if (_textiowrapper_writeflush(self) < 0)