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

Skip to content

Commit e05565e

Browse files
committed
Issue #12213: Fix a buffering bug with interleaved reads and writes that
could appear on BufferedRandom streams.
1 parent a370fcf commit e05565e

3 files changed

Lines changed: 103 additions & 61 deletions

File tree

Lib/test/test_io.py

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

1398-
rw.write(b"asdf")
1398+
rw.write(b"123f")
13991399
rw.seek(0, 0)
1400-
self.assertEqual(b"asdfasdfl", rw.read())
1400+
self.assertEqual(b"asdf123fl", rw.read())
14011401
self.assertEqual(9, rw.tell())
14021402
rw.seek(-4, 2)
14031403
self.assertEqual(5, rw.tell())
14041404
rw.seek(2, 1)
14051405
self.assertEqual(7, rw.tell())
14061406
self.assertEqual(b"fl", rw.read(11))
1407+
rw.flush()
1408+
self.assertEqual(b"asdf123fl", raw.getvalue())
1409+
14071410
self.assertRaises(TypeError, rw.seek, 0.0)
14081411

14091412
def check_flush_and_read(self, read_func):
@@ -1548,6 +1551,43 @@ def test_misbehaved_io(self):
15481551
BufferedReaderTest.test_misbehaved_io(self)
15491552
BufferedWriterTest.test_misbehaved_io(self)
15501553

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

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Core and Builtins
1919
Library
2020
-------
2121

22+
- Issue #12213: Fix a buffering bug with interleaved reads and writes that
23+
could appear on BufferedRandom streams.
24+
2225
- Issue #12326: sys.platform is now always 'linux2' on Linux, even if Python
2326
is compiled on Linux 3.
2427

Modules/_io/bufferedio.c

Lines changed: 58 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -752,25 +752,38 @@ _trap_eintr(void)
752752
*/
753753

754754
static PyObject *
755-
buffered_flush(buffered *self, PyObject *args)
755+
buffered_flush_and_rewind_unlocked(buffered *self)
756756
{
757757
PyObject *res;
758758

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

776789
return res;
@@ -791,7 +804,7 @@ buffered_peek(buffered *self, PyObject *args)
791804
return NULL;
792805

793806
if (self->writable) {
794-
res = _bufferedwriter_flush_unlocked(self, 1);
807+
res = buffered_flush_and_rewind_unlocked(self);
795808
if (res == NULL)
796809
goto end;
797810
Py_CLEAR(res);
@@ -826,19 +839,18 @@ buffered_read(buffered *self, PyObject *args)
826839
if (!ENTER_BUFFERED(self))
827840
return NULL;
828841
res = _bufferedreader_read_all(self);
829-
LEAVE_BUFFERED(self)
830842
}
831843
else {
832844
res = _bufferedreader_read_fast(self, n);
833-
if (res == Py_None) {
834-
Py_DECREF(res);
835-
if (!ENTER_BUFFERED(self))
836-
return NULL;
837-
res = _bufferedreader_read_generic(self, n);
838-
LEAVE_BUFFERED(self)
839-
}
845+
if (res != Py_None)
846+
return res;
847+
Py_DECREF(res);
848+
if (!ENTER_BUFFERED(self))
849+
return NULL;
850+
res = _bufferedreader_read_generic(self, n);
840851
}
841852

853+
LEAVE_BUFFERED(self)
842854
return res;
843855
}
844856

@@ -864,13 +876,6 @@ buffered_read1(buffered *self, PyObject *args)
864876
if (!ENTER_BUFFERED(self))
865877
return NULL;
866878

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

@@ -890,6 +895,13 @@ buffered_read1(buffered *self, PyObject *args)
890895
goto end;
891896
}
892897

898+
if (self->writable) {
899+
res = buffered_flush_and_rewind_unlocked(self);
900+
if (res == NULL)
901+
goto end;
902+
Py_DECREF(res);
903+
}
904+
893905
/* Fill the buffer from the raw stream, and copy it to the result. */
894906
_bufferedreader_reset_buf(self);
895907
r = _bufferedreader_fill_buffer(self);
@@ -912,24 +924,10 @@ buffered_read1(buffered *self, PyObject *args)
912924
static PyObject *
913925
buffered_readinto(buffered *self, PyObject *args)
914926
{
915-
PyObject *res = NULL;
916-
917927
CHECK_INITIALIZED(self)
918928

919-
/* TODO: use raw.readinto() instead! */
920-
if (self->writable) {
921-
if (!ENTER_BUFFERED(self))
922-
return NULL;
923-
res = _bufferedwriter_flush_unlocked(self, 0);
924-
LEAVE_BUFFERED(self)
925-
if (res == NULL)
926-
goto end;
927-
Py_DECREF(res);
928-
}
929-
res = bufferediobase_readinto((PyObject *)self, args);
930-
931-
end:
932-
return res;
929+
/* TODO: use raw.readinto() (or a direct copy from our buffer) instead! */
930+
return bufferediobase_readinto((PyObject *)self, args);
933931
}
934932

935933
static PyObject *
@@ -967,12 +965,6 @@ _buffered_readline(buffered *self, Py_ssize_t limit)
967965
goto end_unlocked;
968966

969967
/* Now we try to get some more from the raw stream */
970-
if (self->writable) {
971-
res = _bufferedwriter_flush_unlocked(self, 1);
972-
if (res == NULL)
973-
goto end;
974-
Py_CLEAR(res);
975-
}
976968
chunks = PyList_New(0);
977969
if (chunks == NULL)
978970
goto end;
@@ -986,9 +978,16 @@ _buffered_readline(buffered *self, Py_ssize_t limit)
986978
}
987979
Py_CLEAR(res);
988980
written += n;
981+
self->pos += n;
989982
if (limit >= 0)
990983
limit -= n;
991984
}
985+
if (self->writable) {
986+
PyObject *r = buffered_flush_and_rewind_unlocked(self);
987+
if (r == NULL)
988+
goto end;
989+
Py_DECREF(r);
990+
}
992991

993992
for (;;) {
994993
_bufferedreader_reset_buf(self);
@@ -1157,20 +1156,11 @@ buffered_truncate(buffered *self, PyObject *args)
11571156
return NULL;
11581157

11591158
if (self->writable) {
1160-
res = _bufferedwriter_flush_unlocked(self, 0);
1159+
res = buffered_flush_and_rewind_unlocked(self);
11611160
if (res == NULL)
11621161
goto end;
11631162
Py_CLEAR(res);
11641163
}
1165-
if (self->readable) {
1166-
if (pos == Py_None) {
1167-
/* Rewind the raw stream so that its position corresponds to
1168-
the current logical position. */
1169-
if (_buffered_raw_seek(self, -RAW_OFFSET(self), 1) == -1)
1170-
goto end;
1171-
}
1172-
_bufferedreader_reset_buf(self);
1173-
}
11741164
res = PyObject_CallMethodObjArgs(self->raw, _PyIO_str_truncate, pos, NULL);
11751165
if (res == NULL)
11761166
goto end;
@@ -1367,17 +1357,18 @@ _bufferedreader_read_all(buffered *self)
13671357
Py_DECREF(chunks);
13681358
return NULL;
13691359
}
1360+
self->pos += current_size;
13701361
}
1371-
_bufferedreader_reset_buf(self);
13721362
/* We're going past the buffer's bounds, flush it */
13731363
if (self->writable) {
1374-
res = _bufferedwriter_flush_unlocked(self, 1);
1364+
res = buffered_flush_and_rewind_unlocked(self);
13751365
if (res == NULL) {
13761366
Py_DECREF(chunks);
13771367
return NULL;
13781368
}
13791369
Py_CLEAR(res);
13801370
}
1371+
_bufferedreader_reset_buf(self);
13811372
while (1) {
13821373
if (data) {
13831374
if (PyList_Append(chunks, data) < 0) {
@@ -1460,6 +1451,14 @@ _bufferedreader_read_generic(buffered *self, Py_ssize_t n)
14601451
memcpy(out, self->buffer + self->pos, current_size);
14611452
remaining -= current_size;
14621453
written += current_size;
1454+
self->pos += current_size;
1455+
}
1456+
/* Flush the write buffer if necessary */
1457+
if (self->writable) {
1458+
PyObject *r = buffered_flush_and_rewind_unlocked(self);
1459+
if (r == NULL)
1460+
goto error;
1461+
Py_DECREF(r);
14631462
}
14641463
_bufferedreader_reset_buf(self);
14651464
while (remaining > 0) {

0 commit comments

Comments
 (0)