Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e8bb1a0

Browse files
committed
Issue #12213: Fix a buffering bug with interleaved reads and writes that
could appear on BufferedRandom streams.
2 parents 8fd544f + e05565e commit e8bb1a0

3 files changed

Lines changed: 102 additions & 46 deletions

File tree

Lib/test/test_io.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,15 +1401,18 @@ def test_seek_and_tell(self):
14011401
rw.seek(0, 0)
14021402
self.assertEqual(b"asdf", rw.read(4))
14031403

1404-
rw.write(b"asdf")
1404+
rw.write(b"123f")
14051405
rw.seek(0, 0)
1406-
self.assertEqual(b"asdfasdfl", rw.read())
1406+
self.assertEqual(b"asdf123fl", rw.read())
14071407
self.assertEqual(9, rw.tell())
14081408
rw.seek(-4, 2)
14091409
self.assertEqual(5, rw.tell())
14101410
rw.seek(2, 1)
14111411
self.assertEqual(7, rw.tell())
14121412
self.assertEqual(b"fl", rw.read(11))
1413+
rw.flush()
1414+
self.assertEqual(b"asdf123fl", raw.getvalue())
1415+
14131416
self.assertRaises(TypeError, rw.seek, 0.0)
14141417

14151418
def check_flush_and_read(self, read_func):
@@ -1554,6 +1557,43 @@ def test_misbehaved_io(self):
15541557
BufferedReaderTest.test_misbehaved_io(self)
15551558
BufferedWriterTest.test_misbehaved_io(self)
15561559

1560+
def test_interleaved_read_write(self):
1561+
# Test for issue #12213
1562+
with self.BytesIO(b'abcdefgh') as raw:
1563+
with self.tp(raw, 100) as f:
1564+
f.write(b"1")
1565+
self.assertEqual(f.read(1), b'b')
1566+
f.write(b'2')
1567+
self.assertEqual(f.read1(1), b'd')
1568+
f.write(b'3')
1569+
buf = bytearray(1)
1570+
f.readinto(buf)
1571+
self.assertEqual(buf, b'f')
1572+
f.write(b'4')
1573+
self.assertEqual(f.peek(1), b'h')
1574+
f.flush()
1575+
self.assertEqual(raw.getvalue(), b'1b2d3f4h')
1576+
1577+
with self.BytesIO(b'abc') as raw:
1578+
with self.tp(raw, 100) as f:
1579+
self.assertEqual(f.read(1), b'a')
1580+
f.write(b"2")
1581+
self.assertEqual(f.read(1), b'c')
1582+
f.flush()
1583+
self.assertEqual(raw.getvalue(), b'a2c')
1584+
1585+
def test_interleaved_readline_write(self):
1586+
with self.BytesIO(b'ab\ncdef\ng\n') as raw:
1587+
with self.tp(raw) as f:
1588+
f.write(b'1')
1589+
self.assertEqual(f.readline(), b'b\n')
1590+
f.write(b'2')
1591+
self.assertEqual(f.readline(), b'def\n')
1592+
f.write(b'3')
1593+
self.assertEqual(f.readline(), b'\n')
1594+
f.flush()
1595+
self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n')
1596+
15571597
# You can't construct a BufferedRandom over a non-seekable stream.
15581598
test_unseekable = None
15591599

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ Core and Builtins
265265
Library
266266
-------
267267

268+
- Issue #12213: Fix a buffering bug with interleaved reads and writes that
269+
could appear on BufferedRandom streams.
270+
268271
- Issue #12778: Reduce memory consumption when JSON-encoding a large
269272
container of many small objects.
270273

Modules/_io/bufferedio.c

Lines changed: 57 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -753,25 +753,38 @@ _trap_eintr(void)
753753
*/
754754

755755
static PyObject *
756-
buffered_flush(buffered *self, PyObject *args)
756+
buffered_flush_and_rewind_unlocked(buffered *self)
757757
{
758758
PyObject *res;
759759

760-
CHECK_INITIALIZED(self)
761-
CHECK_CLOSED(self, "flush of closed file")
762-
763-
if (!ENTER_BUFFERED(self))
764-
return NULL;
765760
res = _bufferedwriter_flush_unlocked(self, 0);
766-
if (res != NULL && self->readable) {
761+
if (res == NULL)
762+
return NULL;
763+
Py_DECREF(res);
764+
765+
if (self->readable) {
767766
/* Rewind the raw stream so that its position corresponds to
768767
the current logical position. */
769768
Py_off_t n;
770769
n = _buffered_raw_seek(self, -RAW_OFFSET(self), 1);
771-
if (n == -1)
772-
Py_CLEAR(res);
773770
_bufferedreader_reset_buf(self);
771+
if (n == -1)
772+
return NULL;
774773
}
774+
Py_RETURN_NONE;
775+
}
776+
777+
static PyObject *
778+
buffered_flush(buffered *self, PyObject *args)
779+
{
780+
PyObject *res;
781+
782+
CHECK_INITIALIZED(self)
783+
CHECK_CLOSED(self, "flush of closed file")
784+
785+
if (!ENTER_BUFFERED(self))
786+
return NULL;
787+
res = buffered_flush_and_rewind_unlocked(self);
775788
LEAVE_BUFFERED(self)
776789

777790
return res;
@@ -792,7 +805,7 @@ buffered_peek(buffered *self, PyObject *args)
792805
return NULL;
793806

794807
if (self->writable) {
795-
res = _bufferedwriter_flush_unlocked(self, 1);
808+
res = buffered_flush_and_rewind_unlocked(self);
796809
if (res == NULL)
797810
goto end;
798811
Py_CLEAR(res);
@@ -827,19 +840,18 @@ buffered_read(buffered *self, PyObject *args)
827840
if (!ENTER_BUFFERED(self))
828841
return NULL;
829842
res = _bufferedreader_read_all(self);
830-
LEAVE_BUFFERED(self)
831843
}
832844
else {
833845
res = _bufferedreader_read_fast(self, n);
834-
if (res == Py_None) {
835-
Py_DECREF(res);
836-
if (!ENTER_BUFFERED(self))
837-
return NULL;
838-
res = _bufferedreader_read_generic(self, n);
839-
LEAVE_BUFFERED(self)
840-
}
846+
if (res != Py_None)
847+
return res;
848+
Py_DECREF(res);
849+
if (!ENTER_BUFFERED(self))
850+
return NULL;
851+
res = _bufferedreader_read_generic(self, n);
841852
}
842853

854+
LEAVE_BUFFERED(self)
843855
return res;
844856
}
845857

@@ -865,13 +877,6 @@ buffered_read1(buffered *self, PyObject *args)
865877
if (!ENTER_BUFFERED(self))
866878
return NULL;
867879

868-
if (self->writable) {
869-
res = _bufferedwriter_flush_unlocked(self, 1);
870-
if (res == NULL)
871-
goto end;
872-
Py_CLEAR(res);
873-
}
874-
875880
/* Return up to n bytes. If at least one byte is buffered, we
876881
only return buffered bytes. Otherwise, we do one raw read. */
877882

@@ -891,6 +896,13 @@ buffered_read1(buffered *self, PyObject *args)
891896
goto end;
892897
}
893898

899+
if (self->writable) {
900+
res = buffered_flush_and_rewind_unlocked(self);
901+
if (res == NULL)
902+
goto end;
903+
Py_DECREF(res);
904+
}
905+
894906
/* Fill the buffer from the raw stream, and copy it to the result. */
895907
_bufferedreader_reset_buf(self);
896908
r = _bufferedreader_fill_buffer(self);
@@ -939,7 +951,7 @@ buffered_readinto(buffered *self, PyObject *args)
939951
goto end_unlocked;
940952

941953
if (self->writable) {
942-
res = _bufferedwriter_flush_unlocked(self, 0);
954+
res = buffered_flush_and_rewind_unlocked(self);
943955
if (res == NULL)
944956
goto end;
945957
Py_CLEAR(res);
@@ -1022,12 +1034,6 @@ _buffered_readline(buffered *self, Py_ssize_t limit)
10221034
goto end_unlocked;
10231035

10241036
/* Now we try to get some more from the raw stream */
1025-
if (self->writable) {
1026-
res = _bufferedwriter_flush_unlocked(self, 1);
1027-
if (res == NULL)
1028-
goto end;
1029-
Py_CLEAR(res);
1030-
}
10311037
chunks = PyList_New(0);
10321038
if (chunks == NULL)
10331039
goto end;
@@ -1041,9 +1047,16 @@ _buffered_readline(buffered *self, Py_ssize_t limit)
10411047
}
10421048
Py_CLEAR(res);
10431049
written += n;
1050+
self->pos += n;
10441051
if (limit >= 0)
10451052
limit -= n;
10461053
}
1054+
if (self->writable) {
1055+
PyObject *r = buffered_flush_and_rewind_unlocked(self);
1056+
if (r == NULL)
1057+
goto end;
1058+
Py_DECREF(r);
1059+
}
10471060

10481061
for (;;) {
10491062
_bufferedreader_reset_buf(self);
@@ -1212,20 +1225,11 @@ buffered_truncate(buffered *self, PyObject *args)
12121225
return NULL;
12131226

12141227
if (self->writable) {
1215-
res = _bufferedwriter_flush_unlocked(self, 0);
1228+
res = buffered_flush_and_rewind_unlocked(self);
12161229
if (res == NULL)
12171230
goto end;
12181231
Py_CLEAR(res);
12191232
}
1220-
if (self->readable) {
1221-
if (pos == Py_None) {
1222-
/* Rewind the raw stream so that its position corresponds to
1223-
the current logical position. */
1224-
if (_buffered_raw_seek(self, -RAW_OFFSET(self), 1) == -1)
1225-
goto end;
1226-
}
1227-
_bufferedreader_reset_buf(self);
1228-
}
12291233
res = PyObject_CallMethodObjArgs(self->raw, _PyIO_str_truncate, pos, NULL);
12301234
if (res == NULL)
12311235
goto end;
@@ -1416,15 +1420,16 @@ _bufferedreader_read_all(buffered *self)
14161420
self->buffer + self->pos, current_size);
14171421
if (data == NULL)
14181422
return NULL;
1423+
self->pos += current_size;
14191424
}
1420-
_bufferedreader_reset_buf(self);
14211425
/* We're going past the buffer's bounds, flush it */
14221426
if (self->writable) {
1423-
res = _bufferedwriter_flush_unlocked(self, 1);
1427+
res = buffered_flush_and_rewind_unlocked(self);
14241428
if (res == NULL)
14251429
return NULL;
14261430
Py_CLEAR(res);
14271431
}
1432+
_bufferedreader_reset_buf(self);
14281433

14291434
if (PyObject_HasAttr(self->raw, _PyIO_str_readall)) {
14301435
chunk = PyObject_CallMethodObjArgs(self->raw, _PyIO_str_readall, NULL);
@@ -1540,6 +1545,14 @@ _bufferedreader_read_generic(buffered *self, Py_ssize_t n)
15401545
memcpy(out, self->buffer + self->pos, current_size);
15411546
remaining -= current_size;
15421547
written += current_size;
1548+
self->pos += current_size;
1549+
}
1550+
/* Flush the write buffer if necessary */
1551+
if (self->writable) {
1552+
PyObject *r = buffered_flush_and_rewind_unlocked(self);
1553+
if (r == NULL)
1554+
goto error;
1555+
Py_DECREF(r);
15431556
}
15441557
_bufferedreader_reset_buf(self);
15451558
while (remaining > 0) {

0 commit comments

Comments
 (0)