8000 [3.13] gh-119506: fix `_io.TextIOWrapper.write()` write during flush … · python/cpython@9be94f9 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9be94f9

Browse files
miss-islingtonchgnrdvmethane
authored
[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 52586f9) Co-authored-by: Radislav Chugunov <52372310+chgnrdv@users.noreply.github.com> Co-authored-by: Inada Naoki <songofacandy@gmail.com>
1 parent d65e145 commit 9be94f9

File tree

3 files changed

+45
-9
lines changed

3 files changed

+45
-9
lines changed

Lib/test/test_io.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4016,6 +4016,28 @@ def write(self, data):
40164016
t.write("x"*chunk_size)
40174017
self.assertEqual([b"abcdef", b"ghi", b"x"*chunk_size], buf._write_stack)
40184018

4019+
def test_issue119506(self):
4020+
chunk_size = 8192
4021+
4022+
class MockIO(self.MockRawIO):
4023+
written = False
4024+
def write(self, data):
4025+
if not self.written:
4026+
self.written = True
4027+
t.write("middle")
4028+
return super().write(data)
4029+
4030+
buf = MockIO()
4031+
t = self.TextIOWrapper(buf)
4032+
t.write("abc")
4033+
t.write("def")
4034+
# writing data which size >= chunk_size cause flushing buffer before write.
4035+
t.write("g" * chunk_size)
4036+
t.flush()
4037+
4038+
self.assertEqual([b"abcdef", b"middle", b"g"*chunk_size],
4039+
buf._write_stack)
4040+
40194041

40204042
class PyTextIOWrapperTest(TextIOWrapperTest):
40214043
io = pyio
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :meth:`!io.TextIOWrapper.write` method breaks internal buffer when the method is called again during flushing internal buffer.

Modules/_io/textio.c

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,16 +1719,26 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
17191719
bytes_len = PyBytes_GET_SIZE(b);
17201720
}
17211721

1722-
if (self->pending_bytes == NULL) {
1723-
self->pending_bytes_count = 0;
1724-
self->pending_bytes = b;
1725-
}
1726-
else if (self->pending_bytes_count + bytes_len > self->chunk_size) {
1727-
// Prevent to concatenate more than chunk_size data.
1728-
if (_textiowrapper_writeflush(self) < 0) {
1729-
Py_DECREF(b);
1730-
return NULL;
1722+
// We should avoid concatinating huge data.
1723+
// Flush the buffer before adding b to the buffer if b is not small.
1724+
// https://github.com/python/cpython/issues/87426
1725+
if (bytes_len >= self->chunk_size) {
1726+
// _textiowrapper_writeflush() calls buffer.write().
1727+
// self->pending_bytes can be appended during buffer->write()
1728+
// or other thread.
1729+
// We need to loop until buffer becomes empty.
1730+
// https://github.com/python/cpython/issues/118138
1731+
// https://github.com/python/cpython/issues/119506
1732+
while (self->pending_bytes != NULL) {
1733+
if (_textiowrapper_writeflush(self) < 0) {
1734+
Py_DECREF(b);
1735+
return NULL;
1736+
}
17311737
}
1738+
}
1739+
1740+
if (self->pending_bytes == NULL) {
1741+
assert(self->pending_bytes_count == 0);
17321742
self->pending_bytes = b;
17331743
}
17341744
else if (!PyList_CheckExact(self->pending_bytes)) {
@@ -1737,6 +1747,9 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
17371747
Py_DECREF(b);
17381748
return NULL;
17391749
}
1750+
// Since Python 3.12, allocating GC object won't trigger GC and release
1751+
// GIL. See https://github.com/python/cpython/issues/97922
1752+
assert(!PyList_CheckExact(self->pending_bytes));
17401753
PyList_SET_ITEM(list, 0, self->pending_bytes);
17411754
PyList_SET_ITEM(list, 1, b);
17421755
self->pending_bytes = list;

0 commit comments

Comments
 (0)
0