Kaydet (Commit) 32cfedeb authored tarafından Antoine Pitrou's avatar Antoine Pitrou

Issue #9550: a BufferedReader could issue an additional read when the

original read request had been satisfied, which can block indefinitely
when the underlying raw IO channel is e.g. a socket.  Report and original
patch by Jason V. Miller.
üst 5ea3d937
...@@ -52,12 +52,14 @@ class MockRawIO: ...@@ -52,12 +52,14 @@ class MockRawIO:
self._read_stack = list(read_stack) self._read_stack = list(read_stack)
self._write_stack = [] self._write_stack = []
self._reads = 0 self._reads = 0
self._extraneous_reads = 0
def read(self, n=None): def read(self, n=None):
self._reads += 1 self._reads += 1
try: try:
return self._read_stack.pop(0) return self._read_stack.pop(0)
except: except:
self._extraneous_reads += 1
return b"" return b""
def write(self, b): def write(self, b):
...@@ -88,6 +90,7 @@ class MockRawIO: ...@@ -88,6 +90,7 @@ class MockRawIO:
try: try:
data = self._read_stack[0] data = self._read_stack[0]
except IndexError: except IndexError:
self._extraneous_reads += 1
return 0 return 0
if data is None: if data is None:
del self._read_stack[0] del self._read_stack[0]
...@@ -824,6 +827,27 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): ...@@ -824,6 +827,27 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests):
self.assertRaises(IOError, bufio.seek, 0) self.assertRaises(IOError, bufio.seek, 0)
self.assertRaises(IOError, bufio.tell) self.assertRaises(IOError, bufio.tell)
def test_no_extraneous_read(self):
# Issue #9550; when the raw IO object has satisfied the read request,
# we should not issue any additional reads, otherwise it may block
# (e.g. socket).
bufsize = 16
for n in (2, bufsize - 1, bufsize, bufsize + 1, bufsize * 2):
rawio = self.MockRawIO([b"x" * n])
bufio = self.tp(rawio, bufsize)
self.assertEqual(bufio.read(n), b"x" * n)
# Simple case: one raw read is enough to satisfy the request.
self.assertEqual(rawio._extraneous_reads, 0,
"failed for {}: {} != 0".format(n, rawio._extraneous_reads))
# A more complex case where two raw reads are needed to satisfy
# the request.
rawio = self.MockRawIO([b"x" * (n - 1), b"x"])
bufio = self.tp(rawio, bufsize)
self.assertEqual(bufio.read(n), b"x" * n)
self.assertEqual(rawio._extraneous_reads, 0,
"failed for {}: {} != 0".format(n, rawio._extraneous_reads))
class CBufferedReaderTest(BufferedReaderTest): class CBufferedReaderTest(BufferedReaderTest):
tp = io.BufferedReader tp = io.BufferedReader
......
...@@ -548,6 +548,7 @@ Trent Mick ...@@ -548,6 +548,7 @@ Trent Mick
Aristotelis Mikropoulos Aristotelis Mikropoulos
Damien Miller Damien Miller
Chad Miller Chad Miller
Jason V. Miller
Jay T. Miller Jay T. Miller
Roman Milner Roman Milner
Andrii V. Mishkovskyi Andrii V. Mishkovskyi
......
...@@ -70,6 +70,11 @@ Extensions ...@@ -70,6 +70,11 @@ Extensions
Library Library
------- -------
- Issue #9550: a BufferedReader could issue an additional read when the
original read request had been satisfied, which could block indefinitely
when the underlying raw IO channel was e.g. a socket. Report and original
patch by Jason V. Miller.
- Issue #3757: thread-local objects now support cyclic garbage collection. - Issue #3757: thread-local objects now support cyclic garbage collection.
Thread-local objects involved in reference cycles will be deallocated Thread-local objects involved in reference cycles will be deallocated
timely by the cyclic GC, even if the underlying thread is still running. timely by the cyclic GC, even if the underlying thread is still running.
......
...@@ -1383,7 +1383,10 @@ _bufferedreader_read_generic(buffered *self, Py_ssize_t n) ...@@ -1383,7 +1383,10 @@ _bufferedreader_read_generic(buffered *self, Py_ssize_t n)
self->pos = 0; self->pos = 0;
self->raw_pos = 0; self->raw_pos = 0;
self->read_end = 0; self->read_end = 0;
while (self->read_end < self->buffer_size) { /* NOTE: when the read is satisfied, we avoid issuing any additional
reads, which could block indefinitely (e.g. on a socket).
See issue #9550. */
while (remaining > 0 && self->read_end < self->buffer_size) {
Py_ssize_t r = _bufferedreader_fill_buffer(self); Py_ssize_t r = _bufferedreader_fill_buffer(self);
if (r == -1) if (r == -1)
goto error; goto error;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment