diff --git a/Lib/asyncio/protocols.py b/Lib/asyncio/protocols.py index 09987b164c66e5..1ead5b15539ba0 100644 --- a/Lib/asyncio/protocols.py +++ b/Lib/asyncio/protocols.py @@ -199,6 +199,7 @@ def process_exited(self): def _feed_data_to_buffered_proto(proto, data): data_len = len(data) + start = 0 while data_len: buf = proto.get_buffer(data_len) buf_len = len(buf) @@ -206,11 +207,11 @@ def _feed_data_to_buffered_proto(proto, data): raise RuntimeError('get_buffer() returned an empty buffer') if buf_len >= data_len: - buf[:data_len] = data + buf[:data_len] = data[start:start + data_len] if start else data proto.buffer_updated(data_len) return else: - buf[:buf_len] = data[:buf_len] + buf[:buf_len] = data[start:start + buf_len] proto.buffer_updated(buf_len) - data = data[buf_len:] - data_len = len(data) + start += buf_len + data_len -= buf_len diff --git a/Lib/test/test_asyncio/test_protocols.py b/Lib/test/test_asyncio/test_protocols.py index 29d3bd22705c6a..9e4c1b390be6c9 100644 --- a/Lib/test/test_asyncio/test_protocols.py +++ b/Lib/test/test_asyncio/test_protocols.py @@ -2,6 +2,7 @@ from unittest import mock import asyncio +from asyncio import protocols def tearDownModule(): @@ -63,5 +64,32 @@ def test_subprocess_protocol(self): self.assertNotHasAttr(sp, '__dict__') -if __name__ == '__main__': +class FeedDataToBufferedProtoTests(unittest.TestCase): + def _make_proto(self, bufsize): + received = bytearray() + buf = bytearray(bufsize) + + class P(asyncio.BufferedProtocol): + def get_buffer(self, sizehint): + return buf + + def buffer_updated(self, nbytes): + received.extend(buf[:nbytes]) + + return P(), received + + def test_large_multi_iteration(self): + proto, received = self._make_proto(64) + data = bytes(range(256)) * 16 + protocols._feed_data_to_buffered_proto(proto, data) + self.assertEqual(bytes(received), data) + + def test_memoryview_input(self): + proto, received = self._make_proto(64) + payload = b"y" * 200 + protocols._feed_data_to_buffered_proto(proto, memoryview(payload)) + self.assertEqual(bytes(received), payload) + + +if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-05-30-22-32-49.gh-issue-150621.6RHpsO.rst b/Misc/NEWS.d/next/Library/2026-05-30-22-32-49.gh-issue-150621.6RHpsO.rst new file mode 100644 index 00000000000000..a266ca4dcadeac --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-30-22-32-49.gh-issue-150621.6RHpsO.rst @@ -0,0 +1,6 @@ +Improve performance of +:func:`!asyncio.protocols._feed_data_to_buffered_proto`, used by the +proactor event loop's read transports (sockets, pipes, subprocess +stdout/stderr) when the protocol is a :class:`~asyncio.BufferedProtocol`. +Total work drops from ``O(N²)`` to ``O(N)`` when the payload exceeds a +single buffer.