summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/test/test_io.py27
-rw-r--r--Misc/NEWS.d/next/Library/2021-02-20-12-15-29.bpo-43260.6znAas.rst2
-rw-r--r--Modules/_io/textio.c20
3 files changed, 46 insertions, 3 deletions
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index cc54d0e..3768b62 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -3767,6 +3767,33 @@ class CTextIOWrapperTest(TextIOWrapperTest):
with self.assertRaises(AttributeError):
del t._CHUNK_SIZE
+ def test_internal_buffer_size(self):
+ # bpo-43260: TextIOWrapper's internal buffer should not store
+ # data larger than chunk size.
+ chunk_size = 8192 # default chunk size, updated later
+
+ class MockIO(self.MockRawIO):
+ def write(self, data):
+ if len(data) > chunk_size:
+ raise RuntimeError
+ return super().write(data)
+
+ buf = MockIO()
+ t = self.TextIOWrapper(buf, encoding="ascii")
+ chunk_size = t._CHUNK_SIZE
+ t.write("abc")
+ t.write("def")
+ # default chunk size is 8192 bytes so t don't write data to buf.
+ self.assertEqual([], buf._write_stack)
+
+ with self.assertRaises(RuntimeError):
+ t.write("x"*(chunk_size+1))
+
+ self.assertEqual([b"abcdef"], buf._write_stack)
+ t.write("ghi")
+ t.write("x"*chunk_size)
+ self.assertEqual([b"abcdef", b"ghi", b"x"*chunk_size], buf._write_stack)
+
class PyTextIOWrapperTest(TextIOWrapperTest):
io = pyio
diff --git a/Misc/NEWS.d/next/Library/2021-02-20-12-15-29.bpo-43260.6znAas.rst b/Misc/NEWS.d/next/Library/2021-02-20-12-15-29.bpo-43260.6znAas.rst
new file mode 100644
index 0000000..f3c21d1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-02-20-12-15-29.bpo-43260.6znAas.rst
@@ -0,0 +1,2 @@
+Fix TextIOWrapper can not flush internal buffer forever after very large
+text is written.
diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c
index f08d14e..03001ec 100644
--- a/Modules/_io/textio.c
+++ b/Modules/_io/textio.c
@@ -1585,6 +1585,8 @@ _textiowrapper_writeflush(textio *self)
ret = PyObject_CallMethodOneArg(self->buffer, _PyIO_str_write, b);
} while (ret == NULL && _PyIO_trap_eintr());
Py_DECREF(b);
+ // NOTE: We cleared buffer but we don't know how many bytes are actually written
+ // when an error occurred.
if (ret == NULL)
return -1;
Py_DECREF(ret);
@@ -1642,7 +1644,10 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
/* XXX What if we were just reading? */
if (self->encodefunc != NULL) {
- if (PyUnicode_IS_ASCII(text) && is_asciicompat_encoding(self->encodefunc)) {
+ if (PyUnicode_IS_ASCII(text) &&
+ // See bpo-43260
+ PyUnicode_GET_LENGTH(text) <= self->chunk_size &&
+ is_asciicompat_encoding(self->encodefunc)) {
b = text;
Py_INCREF(b);
}
@@ -1651,8 +1656,9 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
}
self->encoding_start_of_stream = 0;
}
- else
+ else {
b = PyObject_CallMethodOneArg(self->encoder, _PyIO_str_encode, text);
+ }
Py_DECREF(text);
if (b == NULL)
@@ -1677,6 +1683,14 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
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;
+ }
+ self->pending_bytes = b;
+ }
else if (!PyList_CheckExact(self->pending_bytes)) {
PyObject *list = PyList_New(2);
if (list == NULL) {
@@ -1696,7 +1710,7 @@ _io_TextIOWrapper_write_impl(textio *self, PyObject *text)
}
self->pending_bytes_count += bytes_len;
- if (self->pending_bytes_count > self->chunk_size || needflush ||
+ if (self->pending_bytes_count >= self->chunk_size || needflush ||
text_needflush) {
if (_textiowrapper_writeflush(self) < 0)
return NULL;