Skip to content

Commit

Permalink
sock.sendall() was deprecated in 0.2.0; remove it
Browse files Browse the repository at this point in the history
  • Loading branch information
njsmith committed Dec 25, 2017
1 parent 61e9a02 commit 2db4e32
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 106 deletions.
29 changes: 5 additions & 24 deletions docs/source/reference-io.rst
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,11 @@ Socket objects
* :meth:`~socket.socket.makefile`: Python's file-like API is
synchronous, so it can't be implemented on top of an async
socket.
* :meth:`~socket.socket.sendall`: Could be supported, but you're
better off using the higher-level
:class:`~trio.SocketStream`, and specifically its
:meth:`~trio.SocketStream.send_all` method, which also does
additional error checking.

In addition, the following methods are similar to the equivalents
in :func:`socket.socket`, but have some trio-specific quirks:
Expand All @@ -375,30 +380,6 @@ Socket objects
left in an unknown state – possibly open, and possibly
closed. The only reasonable thing to do is to close it.

.. method:: sendall(data, flags=0)
:async:

.. deprecated:: 0.2.0
Use :class:`trio.SocketStream` and its ``send_all`` method instead.

Send the data to the socket, blocking until all of it has been
accepted by the operating system.

``flags`` are passed on to ``send``.

.. warning:: If two tasks call this method simultaneously on the
same socket, then their data may end up intermingled on the
wire. This is almost certainly not what you want. Use the
highlevel interface instead (:meth:`trio.SocketStream.send_all`);
it reliably detects this error.

Most low-level operations in trio provide a guarantee: if they raise
:exc:`trio.Cancelled`, this means that they had no effect, so the
system remains in a known state. This is **not true** for
:meth:`sendall`. If this operation raises :exc:`trio.Cancelled` (or
any other exception for that matter), then it may have sent some, all,
or none of the requested data, and there is no way to know which.

.. method:: sendfile

`Not implemented yet! <https://github.com/python-trio/trio/issues/45>`__
Expand Down
10 changes: 9 additions & 1 deletion trio/_highlevel_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,15 @@ async def send_all(self, data):
raise ClosedStreamError("can't send data after sending EOF")
with self._send_conflict_detector.sync:
with _translate_socket_errors_to_stream_errors():
await self.socket._sendall(data)
with memoryview(data) as data:
if not data:
await _core.checkpoint()
return
total_sent = 0
while total_sent < len(data):
with data[total_sent:] as remaining:
sent = await self.socket.send(remaining)
total_sent += sent

async def wait_send_all_might_not_block(self):
async with self._send_conflict_detector:
Expand Down
28 changes: 3 additions & 25 deletions trio/_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,29 +803,6 @@ async def sendmsg(self, *args):
_core.wait_socket_writable
)

################################################################
# sendall
################################################################

# XX: When we remove sendall(), we should move this code (and its test)
# into SocketStream.send_all().
async def _sendall(self, data, flags=0):
with memoryview(data) as data:
if not data:
await _core.checkpoint()
return
total_sent = 0
while total_sent < len(data):
with data[total_sent:] as remaining:
sent = await self.send(remaining, flags)
total_sent += sent

@deprecated(
"0.2.0", issue=291, instead="the high-level SocketStream interface"
)
async def sendall(self, data, flags=0):
return await self._sendall(data, flags)

################################################################
# sendfile
################################################################
Expand All @@ -835,7 +812,8 @@ async def sendall(self, data, flags=0):
# XX

# Intentionally omitted:
# sendall
# makefile
# setblocking
# settimeout
# setblocking/getblocking
# settimeout/gettimeout
# timeout
55 changes: 55 additions & 0 deletions trio/tests/test_highlevel_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,61 @@ async def test_SocketStream_basics():
assert isinstance(b, bytes)


async def test_SocketStream_send_all():
BIG = 10000000

a_sock, b_sock = tsocket.socketpair()
with a_sock, b_sock:
a = SocketStream(a_sock)
b = SocketStream(b_sock)

# Check a send_all that has to be split into multiple parts (on most
# platforms... on Windows every send() either succeeds or fails as a
# whole)
async def sender():
data = bytearray(BIG)
await a.send_all(data)
# send_all uses memoryviews internally, which temporarily "lock"
# the object they view. If it doesn't clean them up properly, then
# some bytearray operations might raise an error afterwards, which
# would be a pretty weird and annoying side-effect to spring on
# users. So test that this doesn't happen, by forcing the
# bytearray's underlying buffer to be realloc'ed:
data += bytes(BIG)
# (Note: the above line of code doesn't do a very good job at
# testing anything, because:
# - on CPython, the refcount GC generally cleans up memoryviews
# for us even if we're sloppy.
# - on PyPy3, at least as of 5.7.0, the memoryview code and the
# bytearray code conspire so that resizing never fails – if
# resizing forces the bytearray's internal buffer to move, then
# all memoryview references are automagically updated (!!).
# See:
# https://gist.github.com/njsmith/0ffd38ec05ad8e34004f34a7dc492227
# But I'm leaving the test here in hopes that if this ever changes
# and we break our implementation of send_all, then we'll get some
# early warning...)

async def receiver():
# Make sure the sender fills up the kernel buffers and blocks
await wait_all_tasks_blocked()
nbytes = 0
while nbytes < BIG:
nbytes += len(await b.receive_some(BIG))
assert nbytes == BIG

async with _core.open_nursery() as nursery:
nursery.start_soon(sender)
nursery.start_soon(receiver)

# We know that we received BIG bytes of NULs so far. Make sure that
# was all the data in there.
await a.send_all(b"e")
assert await b.receive_some(10) == b"e"
await a.send_eof()
assert await b.receive_some(10) == b""


async def fill_stream(s):
async def sender():
while True:
Expand Down
56 changes: 0 additions & 56 deletions trio/tests/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,62 +709,6 @@ async def test_send_recv_variants():
assert await b.recv(10) == b"yyy"


# XX: when we remove sendall(), then this test should be:
# - moved to the SocketStream tests
# - have the recwarn fixture removed (currently used to suppress the
# deprecation warnings that it's issuing)
async def test_SocketType_sendall(recwarn):
BIG = 10000000

a, b = tsocket.socketpair()
with a, b:
# Check a sendall that has to be split into multiple parts (on most
# platforms... on Windows every send() either succeeds or fails as a
# whole)
async def sender():
data = bytearray(BIG)
await a.sendall(data)
# sendall uses memoryviews internally, which temporarily "lock"
# the object they view. If it doesn't clean them up properly, then
# some bytearray operations might raise an error afterwards, which
# would be a pretty weird and annoying side-effect to spring on
# users. So test that this doesn't happen, by forcing the
# bytearray's underlying buffer to be realloc'ed:
data += bytes(BIG)
# (Note: the above line of code doesn't do a very good job at
# testing anything, because:
# - on CPython, the refcount GC generally cleans up memoryviews
# for us even if we're sloppy.
# - on PyPy3, at least as of 5.7.0, the memoryview code and the
# bytearray code conspire so that resizing never fails – if
# resizing forces the bytearray's internal buffer to move, then
# all memoryview references are automagically updated (!!).
# See:
# https://gist.github.com/njsmith/0ffd38ec05ad8e34004f34a7dc492227
# But I'm leaving the test here in hopes that if this ever changes
# and we break our implementation of sendall, then we'll get some
# early warning...)

async def receiver():
# Make sure the sender fills up the kernel buffers and blocks
await wait_all_tasks_blocked()
nbytes = 0
while nbytes < BIG:
nbytes += len(await b.recv(BIG))
assert nbytes == BIG

async with _core.open_nursery() as nursery:
nursery.start_soon(sender)
nursery.start_soon(receiver)

# We know that we received BIG bytes of NULs so far. Make sure that
# was all the data in there.
await a.sendall(b"e")
assert await b.recv(10) == b"e"
a.shutdown(tsocket.SHUT_WR)
assert await b.recv(10) == b""


async def test_idna(monkeygai):
# This is the encoding for "faß.de", which uses one of the characters that
# IDNA 2003 handles incorrectly:
Expand Down

0 comments on commit 2db4e32

Please sign in to comment.