diff --git a/docs/source/reference-core.rst b/docs/source/reference-core.rst
index 0c614607d..36f5b14a7 100644
--- a/docs/source/reference-core.rst
+++ b/docs/source/reference-core.rst
@@ -154,7 +154,7 @@ kind of issue looks like in real life, consider this function::
async def recv_exactly(sock, nbytes):
data = bytearray()
while nbytes > 0:
- # SocketType.recv() reads up to 'nbytes' bytes each time
+ # recv() reads up to 'nbytes' bytes each time
chunk += await sock.recv(nbytes)
if not chunk:
raise RuntimeError("socket unexpected closed")
diff --git a/docs/source/reference-io.rst b/docs/source/reference-io.rst
index 66fc34884..d4ce3b17d 100644
--- a/docs/source/reference-io.rst
+++ b/docs/source/reference-io.rst
@@ -5,8 +5,8 @@ I/O in Trio
.. note::
- Please excuse our dust! `geocities-construction-worker.gif
- `__
+ Please excuse our dust! `[insert geocities construction worker gif
+ here] `__
You're looking at the documentation for trio's development branch,
which is currently about half-way through implementing a proper
@@ -162,11 +162,11 @@ create a :class:`SSLStream`:
:members:
-Low-level sockets and networking
---------------------------------
-
.. module:: trio.socket
+Low-level networking with :mod:`trio.socket`
+---------------------------------------------
+
The :mod:`trio.socket` module provides trio's basic low-level
networking API. If you're doing ordinary things with stream-oriented
connections over IPv4/IPv6/Unix domain sockets, then you probably want
@@ -176,8 +176,8 @@ get direct access to all the quirky bits of your system's networking
API, then you're in the right place.
-:mod:`trio.socket`: top-level exports
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Top-level exports
+~~~~~~~~~~~~~~~~~
Generally, the API exposed by :mod:`trio.socket` mirrors that of the
standard library :mod:`socket` module. Most constants (like
@@ -185,13 +185,26 @@ standard library :mod:`socket` module. Most constants (like
are simply re-exported unchanged. But there are also some differences,
which are described here.
-All functions that return socket objects (e.g. :func:`socket.socket`,
-:func:`socket.socketpair`, ...) are modified to return trio socket
-objects instead. In addition, there is a new function to directly
-convert a standard library socket into a trio socket:
+.. function:: socket(...)
+ socketpair(...)
+ fromfd(...)
+ fromshare(...)
+
+ Trio provides analogues to all the standard library functions that
+ return socket objects; their interface is identical, except that
+ they're modified to return trio socket objects instead.
+
+In addition, there is a new function to directly convert a standard
+library socket into a trio socket:
.. autofunction:: from_stdlib_socket
+Unlike :func:`socket.socket`, :func:`trio.socket.socket` is a
+function, not a class; if you want to check whether an object is a
+trio socket, use:
+
+.. autofunction:: is_trio_socket
+
For name lookup, Trio provides the standard functions, but with some
changes:
@@ -237,7 +250,7 @@ broken features:
Socket objects
~~~~~~~~~~~~~~
-.. class:: SocketType()
+.. interface:: The trio socket object interface
Trio socket objects are overall very similar to the :ref:`standard
library socket objects `, with a few
@@ -279,9 +292,27 @@ Socket objects
this can be easily accomplished by calling either
:meth:`resolve_local_address` or :meth:`resolve_remote_address`.
- .. automethod:: resolve_local_address
+ .. method:: resolve_local_address(address)
+
+ Resolve the given address into a numeric address suitable for
+ passing to :meth:`bind`.
+
+ This performs the same address resolution that the standard library
+ :meth:`~socket.socket.bind` call would do, taking into account the
+ current socket's settings (e.g. if this is an IPv6 socket then it
+ returns IPv6 addresses). In particular, a hostname of ``None`` is
+ mapped to the wildcard address.
- .. automethod:: resolve_remote_address
+ .. method:: resolve_remote_address(address)
+
+ Resolve the given address into a numeric address suitable for
+ passing to :meth:`connect` or similar.
+
+ This performs the same address resolution that the standard library
+ :meth:`~socket.socket.connect` call would do, taking into account the
+ current socket's settings (e.g. if this is an IPv6 socket then it
+ returns IPv6 addresses). In particular, a hostname of ``None`` is
+ mapped to the localhost address.
**Modern defaults:** And finally, we took the opportunity to update
the defaults for several socket options that were stuck in the
@@ -326,14 +357,54 @@ Socket objects
See `issue #72 `__ for
discussion of these defaults.
- The following methods are similar, but not identical, to the
- equivalents in :func:`socket.socket`:
+ The following methods are similar to the equivalents in
+ :func:`socket.socket`, but have some trio-specific quirks:
+
+ .. method:: bind
- .. automethod:: bind
+ Bind this socket to the given address.
- .. automethod:: connect
+ Unlike the stdlib :meth:`~socket.socket.bind`, this method
+ requires a pre-resolved address. See
+ :meth:`resolve_local_address`.
- .. automethod:: sendall
+ .. method:: connect
+ :async:
+
+ Connect the socket to a remote address.
+
+ Similar to :meth:`socket.socket.connect`, except async and
+ requiring a pre-resolved address. See
+ :meth:`resolve_remote_address`.
+
+ .. warning::
+
+ Due to limitations of the underlying operating system APIs, it is
+ not always possible to properly cancel a connection attempt once it
+ has begun. If :meth:`connect` is cancelled, and is unable to
+ abort the connection attempt, then it will:
+
+ 1. forcibly close the socket to prevent accidental re-use
+ 2. raise :exc:`~trio.Cancelled`.
+
+ tl;dr: if :meth:`connect` is cancelled then the socket is
+ 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:
+
+ Send the data to the socket, blocking until all of it has been
+ accepted by the operating system.
+
+ ``flags`` are passed on to ``send``.
+
+ 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
@@ -374,6 +445,7 @@ Socket objects
* :meth:`~socket.socket.set_inheritable`
* :meth:`~socket.socket.get_inheritable`
+
Asynchronous disk I/O
---------------------
diff --git a/trio/_network.py b/trio/_network.py
index e0bf67f3a..6df9c666c 100644
--- a/trio/_network.py
+++ b/trio/_network.py
@@ -35,8 +35,8 @@ class SocketStream(HalfCloseableStream):
interface based on a raw network socket.
Args:
- sock (trio.socket.SocketType): The trio socket object to wrap. Must have
- type ``SOCK_STREAM``, and be connected.
+ sock: The trio socket object to wrap. Must have type ``SOCK_STREAM``,
+ and be connected.
By default, :class:`SocketStream` enables ``TCP_NODELAY``, and (on
platforms where it's supported) enables ``TCP_NOTSENT_LOWAT`` with a
@@ -50,12 +50,12 @@ class SocketStream(HalfCloseableStream):
.. attribute:: socket
- The :class:`trio.socket.SocketType` object that this stream wraps.
+ The Trio socket object that this stream wraps.
"""
def __init__(self, sock):
- if not isinstance(sock, tsocket.SocketType):
- raise TypeError("SocketStream requires trio.socket.SocketType")
+ if not tsocket.is_trio_socket(sock):
+ raise TypeError("SocketStream requires trio socket object")
if sock._real_type != tsocket.SOCK_STREAM:
raise ValueError("SocketStream requires a SOCK_STREAM socket")
try:
diff --git a/trio/socket.py b/trio/socket.py
index 0ab70d2ef..412e52f2f 100644
--- a/trio/socket.py
+++ b/trio/socket.py
@@ -176,7 +176,7 @@ def from_stdlib_socket(sock):
"""Convert a standard library :func:`socket.socket` into a trio socket.
"""
- return SocketType(sock)
+ return _SocketType(sock)
__all__.append("from_stdlib_socket")
@_wraps(_stdlib_socket.fromfd, assigned=(), updated=())
@@ -192,9 +192,8 @@ def fromshare(*args, **kwargs):
@_wraps(_stdlib_socket.socketpair, assigned=(), updated=())
def socketpair(*args, **kwargs):
- return tuple(
- from_stdlib_socket(s)
- for s in _stdlib_socket.socketpair(*args, **kwargs))
+ left, right = _stdlib_socket.socketpair(*args, **kwargs)
+ return (from_stdlib_socket(left), from_stdlib_socket(right))
__all__.append("socketpair")
@_wraps(_stdlib_socket.socket, assigned=(), updated=())
@@ -204,7 +203,19 @@ def socket(*args, **kwargs):
################################################################
-# SocketType
+# Type checking
+################################################################
+
+def is_trio_socket(obj):
+ """Check whether the given object is a trio socket.
+
+ """
+ return isinstance(obj, _SocketType)
+
+__all__.append("is_trio_socket")
+
+################################################################
+# _SocketType
################################################################
# sock.type gets weird stuff set in it, in particular on Linux:
@@ -218,7 +229,7 @@ def socket(*args, **kwargs):
getattr(_stdlib_socket, "SOCK_NONBLOCK", 0)
| getattr(_stdlib_socket, "SOCK_CLOEXEC", 0))
-class SocketType:
+class _SocketType:
def __init__(self, sock):
if type(sock) is not _stdlib_socket.socket:
# For example, ssl.SSLSocket subclasses socket.socket, but we
@@ -302,15 +313,9 @@ def dup(self):
"""Same as :meth:`socket.socket.dup`.
"""
- return SocketType(self._sock.dup())
+ return _SocketType(self._sock.dup())
def bind(self, address):
- """Bind this socket to the given address.
-
- Unlike the stdlib :meth:`~socket.socket.connect`, this method requires
- a pre-resolved address. See :meth:`resolve_local_address`.
-
- """
self._check_address(address, require_resolved=True)
return self._sock.bind(address)
@@ -418,30 +423,10 @@ async def _resolve_address(self, address, flags):
# Returns something appropriate to pass to bind()
async def resolve_local_address(self, address):
- """Resolve the given address into a numeric address suitable for
- passing to :meth:`bind`.
-
- This performs the same address resolution that the standard library
- :meth:`~socket.socket.bind` call would do, taking into account the
- current socket's settings (e.g. if this is an IPv6 socket then it
- returns IPv6 addresses). In particular, a hostname of ``None`` is
- mapped to the wildcard address.
-
- """
return await self._resolve_address(address, AI_PASSIVE)
# Returns something appropriate to pass to connect()/sendto()/sendmsg()
async def resolve_remote_address(self, address):
- """Resolve the given address into a numeric address suitable for
- passing to :meth:`connect` or similar.
-
- This performs the same address resolution that the standard library
- :meth:`~socket.socket.connect` call would do, taking into account the
- current socket's settings (e.g. if this is an IPv6 socket then it
- returns IPv6 addresses). In particular, a hostname of ``None`` is
- mapped to the localhost address.
-
- """
return await self._resolve_address(address, 0)
async def _nonblocking_helper(self, fn, args, kwargs, wait_fn):
@@ -510,28 +495,10 @@ async def accept(self):
################################################################
async def connect(self, address):
- """Connect the socket to a remote address.
-
- Similar to :meth:`socket.socket.connect`, except async and requiring a
- pre-resolved address. See :meth:`resolve_remote_address`.
-
- .. warning::
-
- Due to limitations of the underlying operating system APIs, it is
- not always possible to properly cancel a connection attempt once it
- has begun. If :meth:`connect` is cancelled, and is unable to
- abort the connection attempt, then it will:
-
- 1. forcibly close the socket to prevent accidental re-use
- 2. raise :exc:`~trio.Cancelled`.
-
- tl;dr: if :meth:`connect` is cancelled then you should throw away
- that socket and make a new one.
-
- """
# nonblocking connect is weird -- you call it to start things
# off, then the socket becomes writable as a completion
- # notification. This means it isn't really cancellable...
+ # notification. This means it isn't really cancellable... we close the
+ # socket if cancelled, to avoid confusion.
async with _try_sync():
self._check_address(address, require_resolved=True)
# An interesting puzzle: can a non-blocking connect() return EINTR
@@ -693,19 +660,6 @@ async def sendmsg(self, *args):
################################################################
async def sendall(self, data, flags=0):
- """Send the data to the socket, blocking until all of it has been
- accepted by the operating system.
-
- ``flags`` are passed on to ``send``.
-
- 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.
-
- """
with memoryview(data) as data:
if not data:
await _core.yield_briefly()
@@ -730,8 +684,6 @@ async def sendall(self, data, flags=0):
# settimeout
# timeout
-__all__.append("SocketType")
-
################################################################
# create_connection
diff --git a/trio/tests/test_socket.py b/trio/tests/test_socket.py
index 207bb64af..9f9129820 100644
--- a/trio/tests/test_socket.py
+++ b/trio/tests/test_socket.py
@@ -181,8 +181,10 @@ async def test_getnameinfo():
async def test_from_stdlib_socket():
sa, sb = stdlib_socket.socketpair()
+ assert not tsocket.is_trio_socket(sa)
with sa, sb:
ta = tsocket.from_stdlib_socket(sa)
+ assert tsocket.is_trio_socket(ta)
assert sa.fileno() == ta.fileno()
await ta.sendall(b"xxx")
assert sb.recv(3) == b"xxx"
@@ -239,16 +241,18 @@ async def test_fromshare():
async def test_socket():
with tsocket.socket() as s:
- assert isinstance(s, tsocket.SocketType)
+ assert isinstance(s, tsocket._SocketType)
+ assert tsocket.is_trio_socket(s)
assert s.family == tsocket.AF_INET
with tsocket.socket(tsocket.AF_INET6, tsocket.SOCK_DGRAM) as s:
- assert isinstance(s, tsocket.SocketType)
+ assert isinstance(s, tsocket._SocketType)
+ assert tsocket.is_trio_socket(s)
assert s.family == tsocket.AF_INET6
################################################################
-# SocketType
+# _SocketType
################################################################
async def test_SocketType_basics():
@@ -307,7 +311,7 @@ async def test_SocketType_dup():
with a, b:
a2 = a.dup()
with a2:
- assert isinstance(a2, tsocket.SocketType)
+ assert isinstance(a2, tsocket._SocketType)
assert a2.fileno() != a.fileno()
a.close()
await a2.sendall(b"xxx")
@@ -519,7 +523,7 @@ async def test_SocketType_connect_paths():
with tsocket.socket() as sock, tsocket.socket() as listener:
listener.bind(("127.0.0.1", 0))
listener.listen()
- # Swap in our weird subclass under the trio.socket.SocketType's
+ # Swap in our weird subclass under the trio.socket._SocketType's
# nose -- and then swap it back out again before we hit
# wait_socket_writable, which insists on a real socket.
class CancelSocket(stdlib_socket.socket):