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

Skip to content

Commit cde976d

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

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
@@ -4066,6 +4066,28 @@ def write(self, data):
40664066
t.write("x"*chunk_size)
40674067
self.assertEqual([b"abcdef", b"ghi", b"x"*chunk_size], buf._write_stack)
40684068

4069+
def test_issue119506(self):
4070+
chunk_size = 8192
4071+
4072+
class MockIO(self.MockRawIO):
4073+
written = False
4074+
def write(self, data):
4075+
if not self.written:
4076+
self.written = True
4077+
t.write("middle")
4078+
return super().write(data)
4079+
4080+
buf = MockIO()
4081+
t = self.TextIOWrapper(buf)
4082+
t.write("abc")
4083+
t.write("def")
4084+
# writing data which size >= chunk_size cause flushing buffer before write.
4085+
t.write("g" * chunk_size)
4086+
t.flush()
4087+
4088+
self.assertEqual([b"abcdef", b"middle", b"g"*chunk_size],
4089+
buf._write_stack)
4090+
40694091

40704092
class PyTextIOWrapperTest(TextIOWrapperTest):
40714093
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
@@ -1723,16 +1723,26 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
17231723
bytes_len = PyBytes_GET_SIZE(b);
17241724
}
17251725

1726-
if (self->pending_bytes == NULL) {
1727-
self->pending_bytes_count = 0;
1728-
self->pending_bytes = b;
1729-
}
1730-
else if (self->pending_bytes_count + bytes_len > self->chunk_size) {
1731-
// Prevent to concatenate more than chunk_size data.
1732-
if (_textiowrapper_writeflush(self) < 0) {
1733-
Py_DECREF(b);
1734-
return NULL;
1726+
// We should avoid concatinating huge data.
1727+
// Flush the buffer before adding b to the buffer if b is not small.
1728+
// https://github.com/python/cpython/issues/87426
1729+
if (bytes_len >= self->chunk_size) {
1730+
// _textiowrapper_writeflush() calls buffer.write().
1731+
// self->pending_bytes can be appended during buffer->write()
1732+
// or other thread.
1733+
// We need to loop until buffer becomes empty.
1734+
// https://github.com/python/cpython/issues/118138
1735+
// https://github.com/python/cpython/issues/119506
1736+
while (self->pending_bytes != NULL) {
1737+
if (_textiowrapper_writeflush(self) < 0) {
1738+
Py_DECREF(b);
1739+
return NULL;
1740+
}
17351741
}
1742+
}
1743+
1744+
if (self->pending_bytes == NULL) {
1745+
assert(self->pending_bytes_count == 0);
17361746
self->pending_bytes = b;
17371747
}
17381748
else if (!PyList_CheckExact(self->pending_bytes)) {
@@ -1741,6 +1751,9 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
17411751
Py_DECREF(b);
17421752
return NULL;
17431753
}
1754+
// Since Python 3.12, allocating GC object won't trigger GC and release
1755+
// GIL. See https://github.com/python/cpython/issues/97922
1756+
assert(!PyList_CheckExact(self->pending_bytes));
17441757
PyList_SET_ITEM(list, 0, self->pending_bytes);
17451758
PyList_SET_ITEM(list, 1, b);
17461759
self->pending_bytes = list;

0 commit comments

Comments
 (0)
0