https://github.com/python/cpython/commit/cde976d85c6632a908dde1ff8695ac5cafd879b6
commit: cde976d85c6632a908dde1ff8695ac5cafd879b6
branch: 3.12
author: Miss Islington (bot) <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2024-06-19T10:23:29Z
summary:

[3.12] gh-119506: fix `_io.TextIOWrapper.write()` write during flush 
(GH-119507) (#119965)

gh-119506: fix `_io.TextIOWrapper.write()` write during flush (GH-119507)
(cherry picked from commit 52586f930f62bd80374f0f240a4ecce0c0238174)

Co-authored-by: Radislav Chugunov <[email protected]>
Co-authored-by: Inada Naoki <[email protected]>

files:
A Misc/NEWS.d/next/Library/2024-05-24-14-32-24.gh-issue-119506.-nMNqq.rst
M Lib/test/test_io.py
M Modules/_io/textio.c

diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index daa40a6ba365b9..8b68653779e733 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -4066,6 +4066,28 @@ def write(self, data):
         t.write("x"*chunk_size)
         self.assertEqual([b"abcdef", b"ghi", b"x"*chunk_size], 
buf._write_stack)
 
+    def test_issue119506(self):
+        chunk_size = 8192
+
+        class MockIO(self.MockRawIO):
+            written = False
+            def write(self, data):
+                if not self.written:
+                    self.written = True
+                    t.write("middle")
+                return super().write(data)
+
+        buf = MockIO()
+        t = self.TextIOWrapper(buf)
+        t.write("abc")
+        t.write("def")
+        # writing data which size >= chunk_size cause flushing buffer before 
write.
+        t.write("g" * chunk_size)
+        t.flush()
+
+        self.assertEqual([b"abcdef", b"middle", b"g"*chunk_size],
+                         buf._write_stack)
+
 
 class PyTextIOWrapperTest(TextIOWrapperTest):
     io = pyio
diff --git 
a/Misc/NEWS.d/next/Library/2024-05-24-14-32-24.gh-issue-119506.-nMNqq.rst 
b/Misc/NEWS.d/next/Library/2024-05-24-14-32-24.gh-issue-119506.-nMNqq.rst
new file mode 100644
index 00000000000000..f9b764ae0c49b3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-05-24-14-32-24.gh-issue-119506.-nMNqq.rst
@@ -0,0 +1 @@
+Fix :meth:`!io.TextIOWrapper.write` method breaks internal buffer when the 
method is called again during flushing internal buffer.
diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c
index 14dd19d95c2498..4a1ba22d381a53 100644
--- a/Modules/_io/textio.c
+++ b/Modules/_io/textio.c
@@ -1723,16 +1723,26 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject 
*text)
         bytes_len = PyBytes_GET_SIZE(b);
     }
 
-    if (self->pending_bytes == NULL) {
-        self->pending_bytes_count = 0;
-        self->pending_bytes = b;
-    }
-    else if (self->pending_bytes_count + bytes_len > self->chunk_size) {
-        // Prevent to concatenate more than chunk_size data.
-        if (_textiowrapper_writeflush(self) < 0) {
-            Py_DECREF(b);
-            return NULL;
+    // We should avoid concatinating huge data.
+    // Flush the buffer before adding b to the buffer if b is not small.
+    // https://github.com/python/cpython/issues/87426
+    if (bytes_len >= self->chunk_size) {
+        // _textiowrapper_writeflush() calls buffer.write().
+        // self->pending_bytes can be appended during buffer->write()
+        // or other thread.
+        // We need to loop until buffer becomes empty.
+        // https://github.com/python/cpython/issues/118138
+        // https://github.com/python/cpython/issues/119506
+        while (self->pending_bytes != NULL) {
+            if (_textiowrapper_writeflush(self) < 0) {
+                Py_DECREF(b);
+                return NULL;
+            }
         }
+    }
+
+    if (self->pending_bytes == NULL) {
+        assert(self->pending_bytes_count == 0);
         self->pending_bytes = b;
     }
     else if (!PyList_CheckExact(self->pending_bytes)) {
@@ -1741,6 +1751,9 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
             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;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to