Skip to content

Commit 792a686

Browse files
committed
Wrap the data in a memoryview when write() buffers it
The send paths are back to plain slicing, since every buffered item is now a memoryview.
1 parent a5f0f6c commit 792a686

2 files changed

Lines changed: 40 additions & 49 deletions

File tree

‎Lib/asyncio/selector_events.py‎

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,7 +1095,9 @@ def write(self, data):
10951095
# Not all was written; register write handler.
10961096
self._add_writer(self._sock_fd, self._write_ready)
10971097

1098-
# Add it to the buffer.
1098+
# Add it to the buffer as a view, so partial sends don't copy it.
1099+
if not isinstance(data, memoryview):
1100+
data = memoryview(data)
10991101
self._buffer.append(data)
11001102
self._buffer_size += len(data)
11011103
self._maybe_pause_protocol()
@@ -1143,9 +1145,6 @@ def _adjust_leftover_buffer(self, nbytes: int) -> None:
11431145
if b_len <= nbytes:
11441146
nbytes -= b_len
11451147
else:
1146-
# Keep a view of the remainder, not a copy.
1147-
if not isinstance(b, memoryview):
1148-
b = memoryview(b)
11491148
buffer.appendleft(b[nbytes:])
11501149
break
11511150

@@ -1157,9 +1156,7 @@ def _write_send(self):
11571156
buffer = self._buffer.popleft()
11581157
n = self._sock.send(buffer)
11591158
if n != len(buffer):
1160-
# Not all data was written; keep a view, not a copy.
1161-
if not isinstance(buffer, memoryview):
1162-
buffer = memoryview(buffer)
1159+
# Not all data was written
11631160
self._buffer.appendleft(buffer[n:])
11641161
self._buffer_size -= n
11651162
except (BlockingIOError, InterruptedError):

‎Lib/test/test_asyncio/test_selector_events.py‎

Lines changed: 36 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -618,16 +618,6 @@ def socket_transport(self, waiter=None, sendmsg=False):
618618
self.addCleanup(close_transport, transport)
619619
return transport
620620

621-
def assert_remainder_is_view(self, transport, buf):
622-
# gh-159503: the unsent remainder is a view, not a copy
623-
rest = transport._buffer[0]
624-
self.assertIsInstance(rest, memoryview)
625-
self.assertIs(rest.obj, memoryview(buf).obj)
626-
if isinstance(buf, bytearray):
627-
# The unsent view pins the bytearray against resizing.
628-
with self.assertRaises(BufferError):
629-
buf.append(0)
630-
631621
def test_ctor(self):
632622
waiter = self.loop.create_future()
633623
tr = self.socket_transport(waiter=waiter)
@@ -865,14 +855,23 @@ def test_write_partial_none(self):
865855
self.assertEqual(list_to_buffer([b'data']), transport._buffer)
866856

867857
def test_write_tryagain(self):
868-
self.sock.send.side_effect = BlockingIOError
869-
870858
data = b'data'
871-
transport = self.socket_transport()
872-
transport.write(data)
859+
for buf in data, bytearray(data), memoryview(data):
860+
with self.subTest(type=type(buf).__name__):
861+
self.sock.send.side_effect = BlockingIOError
873862

874-
self.loop.assert_writer(7, transport._write_ready)
875-
self.assertEqual(list_to_buffer([b'data']), transport._buffer)
863+
transport = self.socket_transport()
864+
transport.write(buf)
865+
866+
self.loop.assert_writer(7, transport._write_ready)
867+
self.assertEqual(list_to_buffer([b'data']), transport._buffer)
868+
# gh-159503: the data is buffered as a view, not a copy
869+
self.assertIsInstance(transport._buffer[0], memoryview)
870+
self.assertIs(transport._buffer[0].obj, memoryview(buf).obj)
871+
if isinstance(buf, bytearray):
872+
# The buffered view pins the bytearray against resizing.
873+
with self.assertRaises(BufferError):
874+
buf.append(0)
876875

877876
def test_write_sendmsg_no_data(self):
878877
self.sock.sendmsg = mock.Mock()
@@ -968,21 +967,19 @@ def test_write_sendmsg_full(self):
968967

969968
@unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg')
970969
def test_write_sendmsg_partial(self):
971-
data = b'data'
972-
for buf in data, bytearray(data), memoryview(data):
973-
with self.subTest(type=type(buf).__name__):
974-
self.sock.sendmsg = mock.Mock()
975-
# Sent partial data
976-
self.sock.sendmsg.return_value = 2
977-
978-
transport = self.socket_transport(sendmsg=True)
979-
transport._buffer.append(buf)
980-
self.loop._add_writer(7, transport._write_ready)
981-
transport._write_ready()
982-
self.assertTrue(self.sock.sendmsg.called)
983-
self.assertTrue(self.loop.writers)
984-
self.assertEqual(list_to_buffer([b'ta']), transport._buffer)
985-
self.assert_remainder_is_view(transport, buf)
970+
971+
data = memoryview(b'data')
972+
self.sock.sendmsg = mock.Mock()
973+
# Sent partial data
974+
self.sock.sendmsg.return_value = 2
975+
976+
transport = self.socket_transport(sendmsg=True)
977+
transport._buffer.append(data)
978+
self.loop._add_writer(7, transport._write_ready)
979+
transport._write_ready()
980+
self.assertTrue(self.sock.sendmsg.called)
981+
self.assertTrue(self.loop.writers)
982+
self.assertEqual(list_to_buffer([b'ta']), transport._buffer)
986983

987984
@unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg')
988985
def test_write_sendmsg_half_buffer(self):
@@ -1083,18 +1080,15 @@ def test_write_ready_no_data(self):
10831080
self.assertRaises(AssertionError, transport._write_ready)
10841081

10851082
def test_write_ready_partial(self):
1086-
data = b'data'
1087-
for buf in data, bytearray(data), memoryview(data):
1088-
with self.subTest(type=type(buf).__name__):
1089-
self.sock.send.return_value = 2
1083+
data = memoryview(b'data')
1084+
self.sock.send.return_value = 2
10901085

1091-
transport = self.socket_transport()
1092-
transport._buffer.append(buf)
1093-
self.loop._add_writer(7, transport._write_ready)
1094-
transport._write_ready()
1095-
self.loop.assert_writer(7, transport._write_ready)
1096-
self.assertEqual(list_to_buffer([b'ta']), transport._buffer)
1097-
self.assert_remainder_is_view(transport, buf)
1086+
transport = self.socket_transport()
1087+
transport._buffer.append(data)
1088+
self.loop._add_writer(7, transport._write_ready)
1089+
transport._write_ready()
1090+
self.loop.assert_writer(7, transport._write_ready)
1091+
self.assertEqual(list_to_buffer([b'ta']), transport._buffer)
10981092

10991093
def test_write_ready_partial_none(self):
11001094
data = b'data'

0 commit comments

Comments
 (0)