]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-151814: Fix unbounded memory growth from repeated empty writes to `io.TextI...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 24 Jun 2026 12:19:03 +0000 (14:19 +0200)
committerGitHub <noreply@github.com>
Wed, 24 Jun 2026 12:19:03 +0000 (12:19 +0000)
(cherry picked from commit c61307222e18bfa06691d53e3ad336c46f432de0)

Co-authored-by: Stan Ulbrych <stan@python.org>
Misc/NEWS.d/next/Library/2026-06-20-21-15-13.gh-issue-151814.OIbgsO.rst [new file with mode: 0644]
Modules/_io/textio.c

diff --git a/Misc/NEWS.d/next/Library/2026-06-20-21-15-13.gh-issue-151814.OIbgsO.rst b/Misc/NEWS.d/next/Library/2026-06-20-21-15-13.gh-issue-151814.OIbgsO.rst
new file mode 100644 (file)
index 0000000..1365fb4
--- /dev/null
@@ -0,0 +1,2 @@
+Fix unbounded memory growth in :class:`io.TextIOWrapper` when repeatedly
+writing an empty string.
index d093514aa66c6ad2a73298ed0ec7b907ddb2df15..3b3d4bed7bfca699f032a97c5b79a4ce257d5311 100644 (file)
@@ -1741,32 +1741,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) {
+    if (bytes_len > 0) {
+        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;
+            }
+            // 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);
-            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;
+
+        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)