Skip to content

Commit 07641dc

Browse files
committed
Address review: slice memoryviews directly, test bytearray, trim NEWS
1 parent 12de092 commit 07641dc

4 files changed

Lines changed: 33 additions & 11 deletions

File tree

‎Doc/library/asyncio-protocol.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ Write-only Transports
333333
This method does not block; it buffers the data and arranges for it
334334
to be sent out asynchronously.
335335

336+
Do not modify *data* until it has been sent.
337+
336338
.. method:: WriteTransport.writelines(list_of_data)
337339

338340
Write a list (or any iterable) of data bytes to the transport.

‎Lib/asyncio/selector_events.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1143,7 +1143,10 @@ def _adjust_leftover_buffer(self, nbytes: int) -> None:
11431143
if b_len <= nbytes:
11441144
nbytes -= b_len
11451145
else:
1146-
buffer.appendleft(memoryview(b)[nbytes:])
1146+
# Keep a view of the remainder, not a copy.
1147+
if not isinstance(b, memoryview):
1148+
b = memoryview(b)
1149+
buffer.appendleft(b[nbytes:])
11471150
break
11481151

11491152
def _write_send(self):
@@ -1154,8 +1157,10 @@ def _write_send(self):
11541157
buffer = self._buffer.popleft()
11551158
n = self._sock.send(buffer)
11561159
if n != len(buffer):
1157-
# Not all data was written
1158-
self._buffer.appendleft(memoryview(buffer)[n:])
1160+
# Not all data was written; keep a view, not a copy.
1161+
if not isinstance(buffer, memoryview):
1162+
buffer = memoryview(buffer)
1163+
self._buffer.appendleft(buffer[n:])
11591164
self._buffer_size -= n
11601165
except (BlockingIOError, InterruptedError):
11611166
self._buffer.appendleft(buffer)

‎Lib/test/test_asyncio/test_selector_events.py‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -959,7 +959,7 @@ def test_write_sendmsg_full(self):
959959
@unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg')
960960
def test_write_sendmsg_partial(self):
961961
data = b'data'
962-
for buf in data, memoryview(data):
962+
for buf in data, bytearray(data), memoryview(data):
963963
with self.subTest(type=type(buf).__name__):
964964
self.sock.sendmsg = mock.Mock()
965965
# Sent partial data
@@ -973,7 +973,15 @@ def test_write_sendmsg_partial(self):
973973
self.assertTrue(self.loop.writers)
974974
self.assertEqual(list_to_buffer([b'ta']), transport._buffer)
975975
# gh-159503: the remainder is a view, not a copy
976-
self.assertIs(transport._buffer[0].obj, data)
976+
rest = transport._buffer[0]
977+
self.assertIsInstance(rest, memoryview)
978+
self.assertIs(
979+
rest.obj,
980+
buf.obj if isinstance(buf, memoryview) else buf)
981+
if isinstance(buf, bytearray):
982+
# The unsent view pins the bytearray against resizing.
983+
with self.assertRaises(BufferError):
984+
buf.append(0)
977985

978986
@unittest.skipUnless(selector_events._HAS_SENDMSG, 'no sendmsg')
979987
def test_write_sendmsg_half_buffer(self):
@@ -1075,7 +1083,7 @@ def test_write_ready_no_data(self):
10751083

10761084
def test_write_ready_partial(self):
10771085
data = b'data'
1078-
for buf in data, memoryview(data):
1086+
for buf in data, bytearray(data), memoryview(data):
10791087
with self.subTest(type=type(buf).__name__):
10801088
self.sock.send.return_value = 2
10811089

@@ -1086,7 +1094,15 @@ def test_write_ready_partial(self):
10861094
self.loop.assert_writer(7, transport._write_ready)
10871095
self.assertEqual(list_to_buffer([b'ta']), transport._buffer)
10881096
# gh-159503: the remainder is a view, not a copy
1089-
self.assertIs(transport._buffer[0].obj, data)
1097+
rest = transport._buffer[0]
1098+
self.assertIsInstance(rest, memoryview)
1099+
self.assertIs(
1100+
rest.obj,
1101+
buf.obj if isinstance(buf, memoryview) else buf)
1102+
if isinstance(buf, bytearray):
1103+
# The unsent view pins the bytearray against resizing.
1104+
with self.assertRaises(BufferError):
1105+
buf.append(0)
10901106

10911107
def test_write_ready_partial_none(self):
10921108
data = b'data'
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
Fix quadratic copying in :mod:`asyncio` selector socket transports when a
2-
large :class:`bytes` write is only partly sent while other data is buffered.
3-
The unsent remainder is now kept as a :class:`memoryview` instead of being
4-
copied on every partial send.
1+
Fix quadratic copying in :mod:`asyncio` selector socket transports on partial
2+
sends. A :class:`bytearray` passed to ``write()`` now stays locked against
3+
resizing until it has been sent.

0 commit comments

Comments
 (0)