mirror of https://github.com/python/cpython
[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 52586f930f
)
Co-authored-by: Radislav Chugunov <52372310+chgnrdv@users.noreply.github.com>
Co-authored-by: Inada Naoki <songofacandy@gmail.com>
This commit is contained in:
parent
d65e145f9d
commit
9be94f9ce6
|
@ -4016,6 +4016,28 @@ class CTextIOWrapperTest(TextIOWrapperTest):
|
||||||
t.write("x"*chunk_size)
|
t.write("x"*chunk_size)
|
||||||
self.assertEqual([b"abcdef", b"ghi", b"x"*chunk_size], buf._write_stack)
|
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):
|
class PyTextIOWrapperTest(TextIOWrapperTest):
|
||||||
io = pyio
|
io = pyio
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix :meth:`!io.TextIOWrapper.write` method breaks internal buffer when the method is called again during flushing internal buffer.
|
|
@ -1719,16 +1719,26 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
|
||||||
bytes_len = PyBytes_GET_SIZE(b);
|
bytes_len = PyBytes_GET_SIZE(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self->pending_bytes == NULL) {
|
// We should avoid concatinating huge data.
|
||||||
self->pending_bytes_count = 0;
|
// Flush the buffer before adding b to the buffer if b is not small.
|
||||||
self->pending_bytes = b;
|
// https://github.com/python/cpython/issues/87426
|
||||||
}
|
if (bytes_len >= self->chunk_size) {
|
||||||
else if (self->pending_bytes_count + bytes_len > self->chunk_size) {
|
// _textiowrapper_writeflush() calls buffer.write().
|
||||||
// Prevent to concatenate more than chunk_size data.
|
// 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) {
|
if (_textiowrapper_writeflush(self) < 0) {
|
||||||
Py_DECREF(b);
|
Py_DECREF(b);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self->pending_bytes == NULL) {
|
||||||
|
assert(self->pending_bytes_count == 0);
|
||||||
self->pending_bytes = b;
|
self->pending_bytes = b;
|
||||||
}
|
}
|
||||||
else if (!PyList_CheckExact(self->pending_bytes)) {
|
else if (!PyList_CheckExact(self->pending_bytes)) {
|
||||||
|
@ -1737,6 +1747,9 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
|
||||||
Py_DECREF(b);
|
Py_DECREF(b);
|
||||||
return NULL;
|
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, 0, self->pending_bytes);
|
||||||
PyList_SET_ITEM(list, 1, b);
|
PyList_SET_ITEM(list, 1, b);
|
||||||
self->pending_bytes = list;
|
self->pending_bytes = list;
|
||||||
|
|
Loading…
Reference in New Issue