Skip to content

Commit 55ba6c8

Browse files
StanFromIrelandmiss-islington
authored andcommitted
pythongh-151814: Fix unbounded memory growth from repeated empty writes to io.TextIOWrapper (pythonGH-151817)
(cherry picked from commit c613072) Co-authored-by: Stan Ulbrych <stan@python.org>
1 parent dcbcf09 commit 55ba6c8

2 files changed

Lines changed: 27 additions & 19 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix unbounded memory growth in :class:`io.TextIOWrapper` when repeatedly
2+
writing an empty string.

Modules/_io/textio.c

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1755,32 +1755,38 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
17551755
}
17561756
}
17571757

1758-
if (self->pending_bytes == NULL) {
1759-
assert(self->pending_bytes_count == 0);
1760-
self->pending_bytes = b;
1761-
}
1762-
else if (!PyList_CheckExact(self->pending_bytes)) {
1763-
PyObject *list = PyList_New(2);
1764-
if (list == NULL) {
1758+
if (bytes_len > 0) {
1759+
if (self->pending_bytes == NULL) {
1760+
assert(self->pending_bytes_count == 0);
1761+
self->pending_bytes = b;
1762+
}
1763+
else if (!PyList_CheckExact(self->pending_bytes)) {
1764+
PyObject *list = PyList_New(2);
1765+
if (list == NULL) {
1766+
Py_DECREF(b);
1767+
return NULL;
1768+
}
1769+
// Since Python 3.12, allocating GC object won't trigger GC and release
1770+
// GIL. See https://github.com/python/cpython/issues/97922
1771+
assert(!PyList_CheckExact(self->pending_bytes));
1772+
PyList_SET_ITEM(list, 0, self->pending_bytes);
1773+
PyList_SET_ITEM(list, 1, b);
1774+
self->pending_bytes = list;
1775+
}
1776+
else {
1777+
if (PyList_Append(self->pending_bytes, b) < 0) {
1778+
Py_DECREF(b);
1779+
return NULL;
1780+
}
17651781
Py_DECREF(b);
1766-
return NULL;
17671782
}
1768-
// Since Python 3.12, allocating GC object won't trigger GC and release
1769-
// GIL. See https://github.com/python/cpython/issues/97922
1770-
assert(!PyList_CheckExact(self->pending_bytes));
1771-
PyList_SET_ITEM(list, 0, self->pending_bytes);
1772-
PyList_SET_ITEM(list, 1, b);
1773-
self->pending_bytes = list;
1783+
1784+
self->pending_bytes_count += bytes_len;
17741785
}
17751786
else {
1776-
if (PyList_Append(self->pending_bytes, b) < 0) {
1777-
Py_DECREF(b);
1778-
return NULL;
1779-
}
17801787
Py_DECREF(b);
17811788
}
17821789

1783-
self->pending_bytes_count += bytes_len;
17841790
if (self->pending_bytes_count >= self->chunk_size || needflush ||
17851791
text_needflush) {
17861792
if (_textiowrapper_writeflush(self) < 0)

0 commit comments

Comments
 (0)