Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/trio/_highlevel_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ async def send_all(self, data: bytes | bytearray | memoryview) -> None:
raise trio.ClosedResourceError("socket was already closed")
await trio.lowlevel.checkpoint()
return
# send() returns a byte count; index memoryviews in bytes too.
# Contiguous multi-byte / multi-dim views must be cast first
# (#3543). Leave empty views alone (cast can fail on strided).
if data.contiguous:
data = data.cast("B")
total_sent = 0
while total_sent < len(data):
with data[total_sent:] as remaining:
Expand Down
29 changes: 29 additions & 0 deletions src/trio/_tests/test_unix_pipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,32 @@ async def test_bizarro_OSError_from_receive() -> None:
@skip_if_fbsd_pipes_broken
async def test_pipe_fully() -> None:
await check_one_way_stream(make_pipe, make_clogged_pipe)


async def test_send_all_memoryview_itemsize_gt_1() -> None:
"""Partial writes must advance by byte count, not element count (#3543)."""
import os

import trio

payload = bytes(range(256)) * 64 # 16 KiB
read_fd, write_fd = os.pipe()
received = bytearray()
async with (
trio.lowlevel.FdStream(write_fd) as sender,
trio.lowlevel.FdStream(
read_fd,
) as receiver,
):

async def send() -> None:
with memoryview(payload).cast("H") as data:
await sender.send_all(data)
await sender.aclose()

async with trio.open_nursery() as nursery:
nursery.start_soon(send)
async for chunk in receiver:
received.extend(chunk)

assert received == payload
7 changes: 6 additions & 1 deletion src/trio/_unix_pipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,14 @@ async def send_all(self, data: bytes | bytearray | memoryview) -> None:
if self._fd_holder.closed:
raise trio.ClosedResourceError("file was already closed")
await trio.lowlevel.checkpoint()
length = len(data)
# adapted from the SocketStream code
with memoryview(data) as view:
# os.write returns a byte count; index memoryviews in bytes too.
# Contiguous multi-byte / multi-dim views must be cast first
# (#3543). Leave empty views alone (cast can fail on strided).
if view.contiguous and view:
view = view.cast("B")
length = len(view)
sent = 0
while sent < length:
with view[sent:] as remaining:
Expand Down
Loading