summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2024-06-19 10:11:07 (GMT)
committerGitHub <noreply@github.com>2024-06-19 10:11:07 (GMT)
commit9be94f9ce6dbc944e9807534664dbde28077f737 (patch)
tree420483e5d8eb15fdd0db311413805f0997993400
parentd65e145f9d778efe28eb5154df1ab9d44e06673a (diff)
downloadcpython-9be94f9ce6dbc944e9807534664dbde28077f737.zip
cpython-9be94f9ce6dbc944e9807534664dbde28077f737.tar.gz
cpython-9be94f9ce6dbc944e9807534664dbde28077f737.tar.bz2
[3.13] gh-119506: fix `_io.TextIOWrapper.write()` write during flush (GH-119507) (#119964)
gh-119506: fix `_io.TextIOWrapper.write()` write during flush (GH-119507) (cherry picked from commit 52586f930f62bd80374f0f240a4ecce0c0238174) Co-authored-by: Radislav Chugunov <52372310+chgnrdv@users.noreply.github.com> Co-authored-by: Inada Naoki <songofacandy@gmail.com>
-rw-r--r--Lib/test/test_io.py22
-rw-r--r--Misc/NEWS.d/next/Library/2024-05-24-14-32-24.gh-issue-119506.-nMNqq.rst1
-rw-r--r--Modules/_io/textio.c31
3 files changed, 45 insertions, 9 deletions
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index e5cb08c..1ca3eda 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -4016,6 +4016,28 @@ class CTextIOWrapperTest(TextIOWrapperTest):
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 0000000..f9b764a
--- /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 9dff8ea..c162d81 100644
--- a/Modules/_io/textio.c
+++ b/Modules/_io/textio.c
@@ -1719,16 +1719,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)) {
@@ -1737,6 +1747,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;