diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index 50c4c5d1c2e..93a21d1af73 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -220,6 +220,7 @@ class StreamReader: if loop is None: loop = events.get_event_loop() self._loop = loop + # TODO: Use a bytearray for a buffer, like the transport. self._buffer = collections.deque() # Deque of bytes objects. self._byte_count = 0 # Bytes in buffer. self._eof = False # Whether we're done. @@ -384,15 +385,23 @@ class StreamReader: if self._exception is not None: raise self._exception - if n <= 0: - return b'' + # There used to be "optimized" code here. It created its own + # Future and waited until self._buffer had at least the n + # bytes, then called read(n). Unfortunately, this could pause + # the transport if the argument was larger than the pause + # limit (which is twice self._limit). So now we just read() + # into a local buffer. - while self._byte_count < n and not self._eof: - assert not self._waiter - self._waiter = futures.Future(loop=self._loop) - try: - yield from self._waiter - finally: - self._waiter = None + blocks = [] + while n > 0: + block = yield from self.read(n) + if not block: + break + blocks.append(block) + n -= len(block) - return (yield from self.read(n)) + # TODO: Raise EOFError if we break before n == 0? (That would + # be a change in specification, but I've always had to add an + # explicit size check to the caller.) + + return b''.join(blocks) diff --git a/Misc/NEWS b/Misc/NEWS index 7750233e363..189e30ba745 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -75,6 +75,8 @@ Core and Builtins Library ------- +- Issue #20154: Deadlock in asyncio.StreamReader.readexactly(). + - Issue #16113: Remove sha3 module again. - Issue #20111: pathlib.Path.with_suffix() now sanity checks the given suffix.