From: Antoine Pitrou Date: Wed, 11 Aug 2010 13:40:17 +0000 (+0000) Subject: Merged revisions 83944 via svnmerge from X-Git-Tag: v2.7.1rc1~419 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=cb4f47c37704dec33135109d4b4d1ba5c31fc26f;p=python Merged revisions 83944 via svnmerge from svn+ssh://pythondev@svn.python.org/python/branches/py3k ........ r83944 | antoine.pitrou | 2010-08-11 15:31:33 +0200 (mer., 11 août 2010) | 6 lines 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. ........ --- diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index b6bf6863c1..f25f710fa4 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -57,12 +57,14 @@ class MockRawIO: self._read_stack = list(read_stack) self._write_stack = [] self._reads = 0 + self._extraneous_reads = 0 def read(self, n=None): self._reads += 1 try: return self._read_stack.pop(0) except: + self._extraneous_reads += 1 return b"" def write(self, b): @@ -93,6 +95,7 @@ class MockRawIO: try: data = self._read_stack[0] except IndexError: + self._extraneous_reads += 1 return 0 if data is None: del self._read_stack[0] @@ -831,6 +834,27 @@ class BufferedReaderTest(unittest.TestCase, CommonBufferedTests): self.assertRaises(IOError, bufio.seek, 0) 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): tp = io.BufferedReader diff --git a/Misc/ACKS b/Misc/ACKS index 9d930e422b..d72427a056 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -528,6 +528,7 @@ Trent Mick Aristotelis Mikropoulos Damien Miller Chad Miller +Jason V. Miller Jay T. Miller Roman Milner Andrii V. Mishkovskyi diff --git a/Misc/NEWS b/Misc/NEWS index b4909faa35..ebfa330919 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -29,6 +29,11 @@ Core and Builtins 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 #9551: Don't raise TypeError when setting the value to None for SafeConfigParser instances constructed with allow_no_value == True. diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index 9a14d26590..a68f8fecf6 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -1388,7 +1388,10 @@ _bufferedreader_read_generic(buffered *self, Py_ssize_t n) self->pos = 0; self->raw_pos = 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); if (r == -1) goto error;