Skip to content

Commit c613072

Browse files
gh-151814: Fix unbounded memory growth from repeated empty writes to io.TextIOWrapper (#151817)
1 parent 22dd5b5 commit c613072

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
@@ -1820,32 +1820,38 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
18201820
}
18211821
}
18221822

1823-
if (self->pending_bytes == NULL) {
1824-
assert(self->pending_bytes_count == 0);
1825-
self->pending_bytes = b;
1826-
}
1827-
else if (!PyList_CheckExact(self->pending_bytes)) {
1828-
PyObject *list = PyList_New(2);
1829-
if (list == NULL) {
1823+
if (bytes_len > 0) {
1824+
if (self->pending_bytes == NULL) {
1825+
assert(self->pending_bytes_count == 0);
1826+
self->pending_bytes = b;
1827+
}
1828+
else if (!PyList_CheckExact(self->pending_bytes)) {
1829+
PyObject *list = PyList_New(2);
1830+
if (list == NULL) {
1831+
Py_DECREF(b);
1832+
return NULL;
1833+
}
1834+
// Since Python 3.12, allocating GC object won't trigger GC and release
1835+
// GIL. See https://github.com/python/cpython/issues/97922
1836+
assert(!PyList_CheckExact(self->pending_bytes));
1837+
PyList_SET_ITEM(list, 0, self->pending_bytes);
1838+
PyList_SET_ITEM(list, 1, b);
1839+
self->pending_bytes = list;
1840+
}
1841+
else {
1842+
if (PyList_Append(self->pending_bytes, b) < 0) {
1843+
Py_DECREF(b);
1844+
return NULL;
1845+
}
18301846
Py_DECREF(b);
1831-
return NULL;
18321847
}
1833-
// Since Python 3.12, allocating GC object won't trigger GC and release
1834-
// GIL. See https://github.com/python/cpython/issues/97922
1835-
assert(!PyList_CheckExact(self->pending_bytes));
1836-
PyList_SET_ITEM(list, 0, self->pending_bytes);
1837-
PyList_SET_ITEM(list, 1, b);
1838-
self->pending_bytes = list;
1848+
1849+
self->pending_bytes_count += bytes_len;
18391850
}
18401851
else {
1841-
if (PyList_Append(self->pending_bytes, b) < 0) {
1842-
Py_DECREF(b);
1843-
return NULL;
1844-
}
18451852
Py_DECREF(b);
18461853
}
18471854

1848-
self->pending_bytes_count += bytes_len;
18491855
if (self->pending_bytes_count >= self->chunk_size || needflush ||
18501856
text_needflush) {
18511857
if (_textiowrapper_writeflush(self) < 0)

0 commit comments

Comments
 (0)