Skip to content

Commit 2f4cca5

Browse files
authored
gh-150621: avoid quadratic bytes slicing in asyncio.protocols._feed_data_to_buffered_proto (#150622)
1 parent 7517074 commit 2f4cca5

2 files changed

Lines changed: 34 additions & 5 deletions

File tree

Lib/asyncio/protocols.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -199,18 +199,19 @@ def process_exited(self):
199199

200200
def _feed_data_to_buffered_proto(proto, data):
201201
data_len = len(data)
202+
start = 0
202203
while data_len:
203204
buf = proto.get_buffer(data_len)
204205
buf_len = len(buf)
205206
if not buf_len:
206207
raise RuntimeError('get_buffer() returned an empty buffer')
207208

208209
if buf_len >= data_len:
209-
buf[:data_len] = data
210+
buf[:data_len] = data[start:] if start else data
210211
proto.buffer_updated(data_len)
211212
return
212213
else:
213-
buf[:buf_len] = data[:buf_len]
214+
buf[:buf_len] = data[start:start + buf_len]
214215
proto.buffer_updated(buf_len)
215-
data = data[buf_len:]
216-
data_len = len(data)
216+
start += buf_len
217+
data_len -= buf_len

Lib/test/test_asyncio/test_protocols.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from unittest import mock
33

44
import asyncio
5+
from asyncio import protocols
56

67

78
def tearDownModule():
@@ -63,5 +64,32 @@ def test_subprocess_protocol(self):
6364
self.assertNotHasAttr(sp, '__dict__')
6465

6566

66-
if __name__ == '__main__':
67+
class FeedDataToBufferedProtoTests(unittest.TestCase):
68+
def _make_proto(self, bufsize):
69+
received = bytearray()
70+
buf = bytearray(bufsize)
71+
72+
class P(asyncio.BufferedProtocol):
73+
def get_buffer(self, sizehint):
74+
return buf
75+
76+
def buffer_updated(self, nbytes):
77+
received.extend(buf[:nbytes])
78+
79+
return P(), received
80+
81+
def test_large_multi_iteration(self):
82+
proto, received = self._make_proto(64)
83+
data = bytes(range(256)) * 16
84+
protocols._feed_data_to_buffered_proto(proto, data)
85+
self.assertEqual(bytes(received), data)
86+
87+
def test_memoryview_input(self):
88+
proto, received = self._make_proto(64)
89+
payload = b"y" * 200
90+
protocols._feed_data_to_buffered_proto(proto, memoryview(payload))
91+
self.assertEqual(bytes(received), payload)
92+
93+
94+
if __name__ == "__main__":
6795
unittest.main()

0 commit comments

Comments
 (0)