I/O in Trio¶
The abstract Stream API¶
Trio provides a set of abstract base classes that define a standard interface for unidirectional and bidirectional byte streams.
Why is this useful? Because it lets you write generic protocol implementations that can work over arbitrary transports, and easily create complex transport configurations. Here’s some examples:
trio.SocketStream
wraps a raw socket (like a TCP connection over the network), and converts it to the standard stream interface.trio.ssl.SSLStream
is a “stream adapter” that can take any object that implements thetrio.abc.Stream
interface, and convert it into an encrypted stream. In trio the standard way to speak SSL over the network is to wrap anSSLStream
around aSocketStream
.If you spawn a subprocess then you can get a
SendStream
that lets you write to its stdin, and aReceiveStream
that lets you read from its stdout. If for some reason you wanted to speak SSL to a subprocess, you could use aStapledStream
to combine its stdin/stdout into a single bidirectionalStream
, and then wrap that in anSSLStream
:ssl_context = trio.ssl.create_default_context() ssl_context.check_hostname = False s = SSLStream(StapledStream(process.stdin, process.stdout), ssl_context)
[Note: subprocess support is not implemented yet, but that’s the plan. Unless it is implemented, and I forgot to remove this note.]
It sometimes happens that you want to connect to an HTTPS server, but you have to go through a web proxy… and the proxy also uses HTTPS. So you end up having to do SSL-on-top-of-SSL. In trio this is trivial – just wrap your first
SSLStream
in a secondSSLStream
:# Get a raw SocketStream connection to the proxy: s0 = await open_tcp_stream("proxy", 443) # Set up SSL connection to proxy: s1 = SSLStream(s0, proxy_ssl_context, server_hostname="proxy") # Request a connection to the website await s1.send_all(b"CONNECT website:443 / HTTP/1.0\r\n") await check_CONNECT_response(s1) # Set up SSL connection to the real website. Notice that s1 is # already an SSLStream object, and here we're wrapping a second # SSLStream object around it. s2 = SSLStream(s1, website_ssl_context, server_hostname="website") # Make our request await s2.send_all("GET /index.html HTTP/1.0\r\n") ...
The
trio.testing
module provides a set of flexible in-memory stream object implementations, so if you have a protocol implementation to test then you can can start two tasks, set up a virtual “socket” connecting them, and then do things like inject random-but-repeatable delays into the connection.
Abstract base classes¶
Abstract base class | Inherits from… | Adds these abstract methods… | And these concrete methods. | Example implementations |
---|---|---|---|---|
AsyncResource |
aclose() |
__aenter__ , __aexit__ |
Asynchronous file objects | |
SendStream |
AsyncResource |
send_all() ,
wait_send_all_might_not_block() |
MemorySendStream |
|
ReceiveStream |
AsyncResource |
receive_some() |
MemoryReceiveStream |
|
Stream |
SendStream , ReceiveStream |
SSLStream |
||
HalfCloseableStream |
Stream |
send_eof() |
SocketStream , StapledStream |
|
Listener |
AsyncResource |
accept() |
SocketListener , SSLListener |
-
class
trio.abc.
AsyncResource
¶ A standard interface for resources that needs to be cleaned up, and where that cleanup may require blocking operations.
This class distinguishes between “graceful” closes, which may perform I/O and thus block, and a “forceful” close, which cannot. For example, cleanly shutting down a TLS-encrypted connection requires sending a “goodbye” message; but if a peer has become non-responsive, then sending this message might block forever, so we may want to just drop the connection instead. Therefore the
aclose()
method is unusual in that it should always close the connection (or at least make its best attempt) even if it fails; failure indicates a failure to achieve grace, not a failure to close the connection.Objects that implement this interface can be used as async context managers, i.e., you can write:
async with create_resource() as some_async_resource: ...
Entering the context manager is synchronous (not a checkpoint); exiting it calls
aclose()
. The default implementations of__aenter__
and__aexit__
should be adequate for all subclasses.-
abstractmethod await
aclose
()¶ Close this resource, possibly blocking.
IMPORTANT: This method may block in order to perform a “graceful” shutdown. But, if this fails, then it still must close any underlying resources before returning. An error from this method indicates a failure to achieve grace, not a failure to close the connection.
For example, suppose we call
aclose()
on a TLS-encrypted connection. This requires sending a “goodbye” message; but if the peer has become non-responsive, then our attempt to send this message might block forever, and eventually time out and be cancelled. In this case theaclose()
method onSSLStream
will immediately close the underlying transport stream usingtrio.aclose_forcefully()
before raisingCancelled
.If the resource is already closed, then this method should silently succeed.
Once this method completes, any other pending or future operations on this resource should generally raise
ClosedResourceError
, unless there’s a good reason to do otherwise.See also:
trio.aclose_forcefully()
.
-
abstractmethod await
-
await
trio.
aclose_forcefully
(resource)¶ Close an async resource or async generator immediately, without blocking to do any graceful cleanup.
AsyncResource
objects guarantee that if theiraclose()
method is cancelled, then they will still close the resource (albeit in a potentially ungraceful fashion).aclose_forcefully()
is a convenience function that exploits this behavior to let you force a resource to be closed without blocking: it works by callingawait resource.aclose()
and then cancelling it immediately.Most users won’t need this, but it may be useful on cleanup paths where you can’t afford to block, or if you want to close a resource and don’t care about handling it gracefully. For example, if
SSLStream
encounters an error and cannot perform its own graceful close, then there’s no point in waiting to gracefully shut down the underlying transport either, so it callsawait aclose_forcefully(self.transport_stream)
.Note that this function is async, and that it acts as a checkpoint, but unlike most async functions it cannot block indefinitely (at least, assuming the underlying resource object is correctly implemented).
-
class
trio.abc.
SendStream
¶ Bases:
trio.abc.AsyncResource
A standard interface for sending data on a byte stream.
The underlying stream may be unidirectional, or bidirectional. If it’s bidirectional, then you probably want to also implement
ReceiveStream
, which makes your object aStream
.Every
SendStream
also implements theAsyncResource
interface.-
abstractmethod await
send_all
(data)¶ Sends the given data through the stream, blocking if necessary.
Parameters: data (bytes, bytearray, or memoryview) – The data to send.
Raises: trio.BusyResourceError
– if another task is already executing asend_all()
,wait_send_all_might_not_block()
, orHalfCloseableStream.send_eof()
on this stream.trio.BrokenResourceError
– if something has gone wrong, and the stream is broken.trio.ClosedResourceError
– if you previously closed this stream object, or if another task closes this stream object whilesend_all()
is running.
Most low-level operations in trio provide a guarantee: if they raise
trio.Cancelled
, this means that they had no effect, so the system remains in a known state. This is not true forsend_all()
. If this operation raisestrio.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.
-
abstractmethod await
wait_send_all_might_not_block
()¶ Block until it’s possible that
send_all()
might not block.This method may return early: it’s possible that after it returns,
send_all()
will still block. (In the worst case, if no better implementation is available, then it might always return immediately without blocking. It’s nice to do better than that when possible, though.)This method must not return late: if it’s possible for
send_all()
to complete without blocking, then it must return. When implementing it, err on the side of returning early.Raises: trio.BusyResourceError
– if another task is already executing asend_all()
,wait_send_all_might_not_block()
, orHalfCloseableStream.send_eof()
on this stream.trio.BrokenResourceError
– if something has gone wrong, and the stream is broken.trio.ClosedResourceError
– if you previously closed this stream object, or if another task closes this stream object whilewait_send_all_might_not_block()
is running.
Note
This method is intended to aid in implementing protocols that want to delay choosing which data to send until the last moment. E.g., suppose you’re working on an implemention of a remote display server like VNC, and the network connection is currently backed up so that if you call
send_all()
now then it will sit for 0.5 seconds before actually sending anything. In this case it doesn’t make sense to take a screenshot, then wait 0.5 seconds, and then send it, because the screen will keep changing while you wait; it’s better to wait 0.5 seconds, then take the screenshot, and then send it, because this way the data you deliver will be more up-to-date. Usingwait_send_all_might_not_block()
makes it possible to implement the better strategy.If you use this method, you might also want to read up on
TCP_NOTSENT_LOWAT
.Further reading:
- Prioritization Only Works When There’s Pending Data to Prioritize
- WWDC 2015: Your App and Next Generation Networks: slides, video and transcript
-
abstractmethod await
-
class
trio.abc.
ReceiveStream
¶ Bases:
trio.abc.AsyncResource
A standard interface for receiving data on a byte stream.
The underlying stream may be unidirectional, or bidirectional. If it’s bidirectional, then you probably want to also implement
SendStream
, which makes your object aStream
.Every
ReceiveStream
also implements theAsyncResource
interface.-
abstractmethod await
receive_some
(max_bytes)¶ Wait until there is data available on this stream, and then return at most
max_bytes
of it.A return value of
b""
(an empty bytestring) indicates that the stream has reached end-of-file. Implementations should be careful that they returnb""
if, and only if, the stream has reached end-of-file!This method will return as soon as any data is available, so it may return fewer than
max_bytes
of data. But it will never return more.Parameters: max_bytes (int) – The maximum number of bytes to return. Must be greater than zero.
Returns: The data received.
Return type: Raises: trio.BusyResourceError
– if two tasks attempt to callreceive_some()
on the same stream at the same time.trio.BrokenResourceError
– if something has gone wrong, and the stream is broken.trio.ClosedResourceError
– if you previously closed this stream object, or if another task closes this stream object whilereceive_some()
is running.
-
abstractmethod await
-
class
trio.abc.
Stream
¶ Bases:
trio.abc.SendStream
,trio.abc.ReceiveStream
A standard interface for interacting with bidirectional byte streams.
A
Stream
is an object that implements both theSendStream
andReceiveStream
interfaces.If implementing this interface, you should consider whether you can go one step further and implement
HalfCloseableStream
.
-
class
trio.abc.
HalfCloseableStream
¶ Bases:
trio.abc.Stream
This interface extends
Stream
to also allow closing the send part of the stream without closing the receive part.-
abstractmethod await
send_eof
()¶ Send an end-of-file indication on this stream, if possible.
The difference between
send_eof()
andaclose()
is thatsend_eof()
is a unidirectional end-of-file indication. After you call this method, you shouldn’t try sending any more data on this stream, and your remote peer should receive an end-of-file indication (eventually, after receiving all the data you sent before that). But, they may continue to send data to you, and you can continue to receive it by callingreceive_some()
. You can think of it as callingaclose()
on just theSendStream
“half” of the stream object (and in fact that’s literally howtrio.StapledStream
implements it).Examples:
- On a socket, this corresponds to
shutdown(..., SHUT_WR)
(man page). - The SSH protocol provides the ability to multiplex bidirectional
“channels” on top of a single encrypted connection. A trio
implementation of SSH could expose these channels as
HalfCloseableStream
objects, and callingsend_eof()
would send anSSH_MSG_CHANNEL_EOF
request (see RFC 4254 §5.3). - On an SSL/TLS-encrypted connection, the protocol doesn’t provide any
way to do a unidirectional shutdown without closing the connection
entirely, so
SSLStream
implementsStream
, notHalfCloseableStream
.
If an EOF has already been sent, then this method should silently succeed.
Raises: trio.BusyResourceError
– if another task is already executing asend_all()
,wait_send_all_might_not_block()
, orsend_eof()
on this stream.trio.BrokenResourceError
– if something has gone wrong, and the stream is broken.trio.ClosedResourceError
– if you previously closed this stream object, or if another task closes this stream object whilesend_eof()
is running.
- On a socket, this corresponds to
-
abstractmethod await
-
class
trio.abc.
Listener
¶ Bases:
trio.abc.AsyncResource
A standard interface for listening for incoming connections.
-
abstractmethod await
accept
()¶ Wait until an incoming connection arrives, and then return it.
Returns: An object representing the incoming connection. In practice this is almost always some variety of
Stream
, though in principle you could also use this interface with, say, SOCK_SEQPACKET sockets or similar.Return type: Raises: trio.BusyResourceError
– if two tasks attempt to callaccept()
on the same listener at the same time.trio.ClosedResourceError
– if you previously closed this listener object, or if another task closes this listener object whileaccept()
is running.
Note that there is no
BrokenListenerError
, because for listeners there is no general condition of “the network/remote peer broke the connection” that can be handled in a generic way, like there is for streams. Other errors can occur and be raised fromaccept()
– for example, if you run out of file descriptors then you might get anOSError
with its errno set toEMFILE
.
-
abstractmethod await
Generic stream tools¶
Trio currently provides a generic helper for writing servers that
listen for connections using one or more
Listener
s, and a generic utility class for working
with streams. And if you want to test code that’s written against the
streams interface, you should also check out Streams in
trio.testing
.
-
await
trio.
serve_listeners
(handler, listeners, *, handler_nursery=None, task_status=TASK_STATUS_IGNORED)¶ Listen for incoming connections on
listeners
, and for each one start a task runninghandler(stream)
.Warning
If
handler
raises an exception, then this function doesn’t do anything special to catch it – so by default the exception will propagate out and crash your server. If you don’t want this, then catch exceptions inside yourhandler
, or use ahandler_nursery
object that responds to exceptions in some other way.Parameters: - handler – An async callable, that will be invoked like
handler_nursery.start_soon(handler, stream)
for each incoming connection. - listeners – A list of
Listener
objects.serve_listeners()
takes responsibility for closing them. - handler_nursery – The nursery used to start handlers, or any object with
a
start_soon
method. IfNone
(the default), thenserve_listeners()
will create a new nursery internally and use that. - task_status – This function can be used with
nursery.start
, which will returnlisteners
.
Returns: This function never returns unless cancelled.
Resource handling:
Error handling:
Most errors coming from
accept()
are allowed to propagate out (crashing the server in the process). However, some errors – those which indicate that the server is temporarily overloaded – are handled specially. These areOSError
s with one of the following errnos:EMFILE
: process is out of file descriptorsENFILE
: system is out of file descriptorsENOBUFS
,ENOMEM
: the kernel hit some sort of memory limitation when trying to create a socket object
When
serve_listeners()
gets one of these errors, then it:- Logs the error to the standard library logger
trio.serve_listeners
(level = ERROR, with exception information included). By default this causes it to be printed to stderr. - Waits 100 ms before calling
accept
again, in hopes that the system will recover.
- handler – An async callable, that will be invoked like
-
class
trio.
StapledStream
(send_stream, receive_stream)¶ Bases:
trio.abc.HalfCloseableStream
This class staples together two unidirectional streams to make single bidirectional stream.
Parameters: - send_stream (SendStream) – The stream to use for sending.
- receive_stream (ReceiveStream) – The stream to use for receiving.
Example
A silly way to make a stream that echoes back whatever you write to it:
left, right = trio.testing.memory_stream_pair() echo_stream = StapledStream(SocketStream(left), SocketStream(right)) await echo_stream.send_all(b"x") assert await echo_stream.receive_some(1) == b"x"
StapledStream
objects implement the methods in theHalfCloseableStream
interface. They also have two additional public attributes:-
send_stream
¶ The underlying
SendStream
.send_all()
andwait_send_all_might_not_block()
are delegated to this object.
-
receive_stream
¶ The underlying
ReceiveStream
.receive_some()
is delegated to this object.
-
await
aclose
()¶ Calls
aclose
on both underlying streams.
-
await
receive_some
(max_bytes)¶ Calls
self.receive_stream.receive_some
.
-
await
send_all
(data)¶ Calls
self.send_stream.send_all
.
-
await
send_eof
()¶ Shuts down the send side of the stream.
If
self.send_stream.send_eof
exists, then calls it. Otherwise, callsself.send_stream.aclose()
.
-
await
wait_send_all_might_not_block
()¶ Calls
self.send_stream.wait_send_all_might_not_block
.
Sockets and networking¶
The high-level network interface is built on top of our stream abstraction.
-
await
trio.
open_tcp_stream
(host, port, *, happy_eyeballs_delay=0.3)¶ Connect to the given host and port over TCP.
If the given
host
has multiple IP addresses associated with it, then we have a problem: which one do we use?One approach would be to attempt to connect to the first one, and then if that fails, attempt to connect to the second one … until we’ve tried all of them. But the problem with this is that if the first IP address is unreachable (for example, because it’s an IPv6 address and our network discards IPv6 packets), then we might end up waiting tens of seconds for the first connection attempt to timeout before we try the second address.
Another approach would be to attempt to connect to all of the addresses at the same time, in parallel, and then use whichever connection succeeds first, abandoning the others. This would be fast, but create a lot of unnecessary load on the network and the remote server.
This function strikes a balance between these two extremes: it works its way through the available addresses one at a time, like the first approach; but, if
happy_eyeballs_delay
seconds have passed and it’s still waiting for an attempt to succeed or fail, then it gets impatient and starts the next connection attempt in parallel. As soon as any one connection attempt succeeds, all the other attempts are cancelled. This avoids unnecessary load because most connections will succeed after just one or two attempts, but if one of the addresses is unreachable then it doesn’t slow us down too much.This is known as a “happy eyeballs” algorithm, and our particular variant is modelled after how Chrome connects to webservers; see RFC 6555 for more details.
Parameters: - host (str or bytes) – The host to connect to. Can be an IPv4 address, IPv6 address, or a hostname.
- port (int) – The port to connect to.
- happy_eyeballs_delay (float) – How many seconds to wait for each
connection attempt to succeed or fail before getting impatient and
starting another one in parallel. Set to
math.inf
if you want to limit to only one connection attempt at a time (likesocket.create_connection()
). Default: 0.3 (300 ms).
Returns: a
Stream
connected to the given server.Return type: Raises: OSError
– if the connection fails.See also
open_ssl_over_tcp_stream
-
await
trio.
serve_tcp
(handler, port, *, host=None, backlog=None, handler_nursery=None, task_status=TASK_STATUS_IGNORED)¶ Listen for incoming TCP connections, and for each one start a task running
handler(stream)
.This is a thin convenience wrapper around
open_tcp_listeners()
andserve_listeners()
– see them for full details.Warning
If
handler
raises an exception, then this function doesn’t do anything special to catch it – so by default the exception will propagate out and crash your server. If you don’t want this, then catch exceptions inside yourhandler
, or use ahandler_nursery
object that responds to exceptions in some other way.When used with
nursery.start
you get back the newly opened listeners. So, for example, if you want to start a server in your test suite and then connect to it to check that it’s working properly, you can use something like:from trio.testing import open_stream_to_socket_listener async with trio.open_nursery() as nursery: listeners = await nursery.start(serve_tcp, handler, 0) client_stream = await open_stream_to_socket_listener(listeners[0]) # Then send and receive data on 'client_stream', for example: await client_stream.send_all(b"GET / HTTP/1.0\r\n\r\n")
This avoids several common pitfalls:
- It lets the kernel pick a random open port, so your test suite doesn’t depend on any particular port being open.
- It waits for the server to be accepting connections on that port before
start
returns, so there’s no race condition where the incoming connection arrives before the server is ready. - It uses the Listener object to find out which port was picked, so it can connect to the right place.
Parameters: - handler – The handler to start for each incoming connection. Passed to
serve_listeners()
. - port – The port to listen on. Use 0 to let the kernel pick an open port.
Passed to
open_tcp_listeners()
. - host (str, bytes, or None) – The host interface to listen on; use
None
to bind to the wildcard address. Passed toopen_tcp_listeners()
. - backlog – The listen backlog, or None to have a good default picked.
Passed to
open_tcp_listeners()
. - handler_nursery – The nursery to start handlers in, or None to use an
internal nursery. Passed to
serve_listeners()
. - task_status – This function can be used with
nursery.start
.
Returns: This function only returns when cancelled.
-
await
trio.
open_ssl_over_tcp_stream
(host, port, *, https_compatible=False, ssl_context=None, happy_eyeballs_delay=0.3)¶ Make a TLS-encrypted Connection to the given host and port over TCP.
This is a convenience wrapper that calls
open_tcp_stream()
and wraps the result in anSSLStream
.This function does not perform the TLS handshake; you can do it manually by calling
do_handshake()
, or else it will be performed automatically the first time you send or receive data.Parameters: - host (bytes or str) – The host to connect to. We require the server to have a TLS certificate valid for this hostname.
- port (int) – The port to connect to.
- https_compatible (bool) – Set this to True if you’re connecting to a web
server. See
SSLStream
for details. Default: False. - ssl_context (
SSLContext
or None) – The SSL context to use. If None (the default),ssl.create_default_context()
will be called to create a context. - happy_eyeballs_delay (float) – See
open_tcp_stream()
.
Returns: the encrypted connection to the server.
Return type:
-
await
trio.
serve_ssl_over_tcp
(handler, port, ssl_context, *, host=None, https_compatible=False, backlog=None, handler_nursery=None, task_status=TASK_STATUS_IGNORED)¶ Listen for incoming TCP connections, and for each one start a task running
handler(stream)
.This is a thin convenience wrapper around
open_ssl_over_tcp_listeners()
andserve_listeners()
– see them for full details.Warning
If
handler
raises an exception, then this function doesn’t do anything special to catch it – so by default the exception will propagate out and crash your server. If you don’t want this, then catch exceptions inside yourhandler
, or use ahandler_nursery
object that responds to exceptions in some other way.When used with
nursery.start
you get back the newly opened listeners. See the documentation forserve_tcp()
for an example where this is useful.Parameters: - handler – The handler to start for each incoming connection. Passed to
serve_listeners()
. - port (int) – The port to listen on. Use 0 to let the kernel pick
an open port. Ultimately passed to
open_tcp_listeners()
. - ssl_context (SSLContext) – The SSL context to use for all incoming
connections. Passed to
open_ssl_over_tcp_listeners()
. - host (str, bytes, or None) – The address to bind to; use
None
to bind to the wildcard address. Ultimately passed toopen_tcp_listeners()
. - https_compatible (bool) – Set this to True if you want to use
“HTTPS-style” TLS. See
SSLStream
for details. - backlog (int or None) – See
SSLStream
for details. - handler_nursery – The nursery to start handlers in, or None to use an
internal nursery. Passed to
serve_listeners()
. - task_status – This function can be used with
nursery.start
.
Returns: This function only returns when cancelled.
- handler – The handler to start for each incoming connection. Passed to
-
await
trio.
open_unix_socket
(filename)¶ Opens a connection to the specified Unix domain socket.
You must have read/write permission on the specified file to connect.
Parameters: filename (str or bytes) – The filename to open the connection to.
Returns: a
Stream
connected to the given file.Return type: Raises: OSError
– If the socket file could not be connected to.RuntimeError
– If AF_UNIX sockets are not supported.
-
class
trio.
SocketStream
(socket)¶ Bases:
trio.abc.HalfCloseableStream
An implementation of the
trio.abc.HalfCloseableStream
interface based on a raw network socket.Parameters: socket – The trio socket object to wrap. Must have type SOCK_STREAM
, and be connected.By default for TCP sockets,
SocketStream
enablesTCP_NODELAY
, and (on platforms where it’s supported) enablesTCP_NOTSENT_LOWAT
with a reasonable buffer size (currently 16 KiB) – see issue #72 for discussion. You can of course override these defaults by callingsetsockopt()
.Once a
SocketStream
object is constructed, it implements the fulltrio.abc.HalfCloseableStream
interface. In addition, it provides a few extra features:-
socket
¶ The Trio socket object that this stream wraps.
-
await
aclose
()¶
-
getsockopt
(level, option, buffersize=0)¶ Check the current value of an option on the underlying socket.
See
socket.socket.getsockopt()
for details.
-
await
receive_some
(max_bytes)¶
-
await
send_all
(data)¶
-
await
send_eof
()¶
-
setsockopt
(level, option, value)¶ Set an option on the underlying socket.
See
socket.socket.setsockopt()
for details.
-
await
wait_send_all_might_not_block
()¶
-
-
class
trio.
SocketListener
(socket)¶ Bases:
trio.abc.Listener
A
Listener
that uses a listening socket to accept incoming connections asSocketStream
objects.Parameters: socket – The trio socket object to wrap. Must have type SOCK_STREAM
, and be listening.Note that the
SocketListener
“takes ownership” of the given socket; closing theSocketListener
will also close the socket.-
socket
¶ The Trio socket object that this stream wraps.
-
await
accept
()¶ Accept an incoming connection.
Returns: Raises: OSError
– if the underlying call toaccept
raises an unexpected error.ClosedResourceError
– if you already closed the socket.
This method handles routine errors like
ECONNABORTED
, but passes other errors on to its caller. In particular, it does not make any special effort to handle resource exhaustion errors likeEMFILE
,ENFILE
,ENOBUFS
,ENOMEM
.
-
await
aclose
()¶ Close this listener and its underlying socket.
-
-
await
trio.
open_tcp_listeners
(port, *, host=None, backlog=None)¶ Create
SocketListener
objects to listen for TCP connections.Parameters: - port (int) –
The port to listen on.
If you use 0 as your port, then the kernel will automatically pick an arbitrary open port. But be careful: if you use this feature when binding to multiple IP addresses, then each IP address will get its own random port, and the returned listeners will probably be listening on different ports. In particular, this will happen if you use
host=None
– which is the default – because in this caseopen_tcp_listeners()
will bind to both the IPv4 wildcard address (0.0.0.0
) and also the IPv6 wildcard address (::
). - host (str, bytes-like, or None) –
The local interface to bind to. This is passed to
getaddrinfo()
with theAI_PASSIVE
flag set.If you want to bind to the wildcard address on both IPv4 and IPv6, in order to accept connections on all available interfaces, then pass
None
. This is the default.If you have a specific interface you want to bind to, pass its IP address or hostname here. If a hostname resolves to multiple IP addresses, this function will open one listener on each of them.
If you want to use only IPv4, or only IPv6, but want to accept on all interfaces, pass the family-specific wildcard address:
"0.0.0.0"
for IPv4-only and"::"
for IPv6-only. - backlog (int or None) – The listen backlog to use. If you leave this as
None
then Trio will pick a good default. (Currently: whatever your system has configured as the maximum backlog.)
Returns: list of
SocketListener
- port (int) –
-
await
trio.
open_ssl_over_tcp_listeners
(port, ssl_context, *, host=None, https_compatible=False, backlog=None)¶ Start listening for SSL/TLS-encrypted TCP connections to the given port.
Parameters: - port (int) – The port to listen on. See
open_tcp_listeners()
. - ssl_context (SSLContext) – The SSL context to use for all incoming connections.
- host (str, bytes, or None) – The address to bind to; use
None
to bind to the wildcard address. Seeopen_tcp_listeners()
. - https_compatible (bool) – See
SSLStream
for details. - backlog (int or None) – See
SSLStream
for details.
- port (int) – The port to listen on. See
SSL / TLS support¶
The trio.ssl
module implements SSL/TLS support for Trio, using
the standard library ssl
module. It re-exports most of
ssl
´s API, with the notable exception of
ssl.SSLContext
, which has unsafe defaults; if you really want
to use ssl.SSLContext
you can import it from ssl
, but
normally you should create your contexts using
trio.ssl.create_default_context
.
Instead of using ssl.SSLContext.wrap_socket()
, though, you
create a SSLStream
:
-
class
trio.ssl.
SSLStream
(transport_stream, ssl_context, *, server_hostname=None, server_side=False, https_compatible=False, max_refill_bytes=32768)¶ Bases:
trio.abc.Stream
Encrypted communication using SSL/TLS.
SSLStream
wraps an arbitraryStream
, and allows you to perform encrypted communication over it using the usualStream
interface. You pass regular data tosend_all()
, then it encrypts it and sends the encrypted data on the underlyingStream
;receive_some()
takes encrypted data out of the underlyingStream
and decrypts it before returning it.You should read the standard library’s
ssl
documentation carefully before attempting to use this class, and probably other general documentation on SSL/TLS as well. SSL/TLS is subtle and quick to anger. Really. I’m not kidding.Parameters: - transport_stream (Stream) – The stream used to transport encrypted data. Required.
- ssl_context (SSLContext) – The
SSLContext
used for this connection. Required. Usually created by callingtrio.ssl.create_default_context()
. - server_hostname (str or None) – The name of the server being connected
to. Used for SNI and for
validating the server’s certificate (if hostname checking is
enabled). This is effectively mandatory for clients, and actually
mandatory if
ssl_context.check_hostname
is True. - server_side (bool) – Whether this stream is acting as a client or server. Defaults to False, i.e. client mode.
- https_compatible (bool) –
There are two versions of SSL/TLS commonly encountered in the wild: the standard version, and the version used for HTTPS (HTTP-over-SSL/TLS).
Standard-compliant SSL/TLS implementations always send a cryptographically signed
close_notify
message before closing the connection. This is important because if the underlying transport were simply closed, then there wouldn’t be any way for the other side to know whether the connection was intentionally closed by the peer that they negotiated a cryptographic connection to, or by some man-in-the-middle attacker who can’t manipulate the cryptographic stream, but can manipulate the transport layer (a so-called “truncation attack”).However, this part of the standard is widely ignored by real-world HTTPS implementations, which means that if you want to interoperate with them, then you NEED to ignore it too.
Fortunately this isn’t as bad as it sounds, because the HTTP protocol already includes its own equivalent of
close_notify
, so doing this again at the SSL/TLS level is redundant. But not all protocols do! Therefore, by default Trio implements the safer standard-compliant version (https_compatible=False
). But if you’re speaking HTTPS or some other protocol whereclose_notify
s are commonly skipped, then you should sethttps_compatible=True
; with this setting, Trio will neither expect nor sendclose_notify
messages.If you have code that was written to use
ssl.SSLSocket
and now you’re porting it to Trio, then it may be useful to know that a difference betweenSSLStream
andssl.SSLSocket
is thatSSLSocket
implements thehttps_compatible=True
behavior by default. - max_refill_bytes (int) –
SSLSocket
maintains an internal buffer of incoming data, and when it runs low then it callsreceive_some()
on the underlying transport stream to refill it. This argument lets you set themax_bytes
argument passed to the underlyingreceive_some()
call. It doesn’t affect calls to this class’sreceive_some()
, or really anything else user-observable except possibly performance. You probably don’t need to worry about this.
-
transport_stream
¶ The underlying transport stream that was passed to
__init__
. An example of when this would be useful is if you’re usingSSLStream
over aSocketStream
and want to call theSocketStream
’ssetsockopt()
method.Type: trio.abc.Stream
Internally, this class is implemented using an instance of
ssl.SSLObject
, and all ofSSLObject
’s methods and attributes are re-exported as methods and attributes on this class.This also means that if you register a SNI callback using
sni_callback
, then the first argument your callback receives will be assl.SSLObject
.-
await
aclose
()¶ Gracefully shut down this connection, and close the underlying transport.
If
https_compatible
is False (the default), then this attempts to first send aclose_notify
and then close the underlying stream by calling itsaclose()
method.If
https_compatible
is set to True, then this simply closes the underlying stream and marks this stream as closed.
-
await
do_handshake
()¶ Ensure that the initial handshake has completed.
The SSL protocol requires an initial handshake to exchange certificates, select cryptographic keys, and so forth, before any actual data can be sent or received. You don’t have to call this method; if you don’t, then
SSLStream
will automatically peform the handshake as needed, the first time you try to send or receive data. But if you want to trigger it manually – for example, because you want to look at the peer’s certificate before you start talking to them – then you can call this method.If the initial handshake is already in progress in another task, this waits for it to complete and then returns.
If the initial handshake has already completed, this returns immediately without doing anything (except executing a checkpoint).
Warning
If this method is cancelled, then it may leave the
SSLStream
in an unusable state. If this happens then any future attempt to use the object will raisetrio.BrokenResourceError
.
-
await
receive_some
(max_bytes)¶ Read some data from the underlying transport, decrypt it, and return it.
See
trio.abc.ReceiveStream.receive_some()
for details.Warning
If this method is cancelled while the initial handshake or a renegotiation are in progress, then it may leave the
SSLStream
in an unusable state. If this happens then any future attempt to use the object will raisetrio.BrokenResourceError
.
-
await
send_all
(data)¶ Encrypt some data and then send it on the underlying transport.
See
trio.abc.SendStream.send_all()
for details.Warning
If this method is cancelled, then it may leave the
SSLStream
in an unusable state. If this happens then any attempt to use the object will raisetrio.BrokenResourceError
.
-
await
unwrap
()¶ Cleanly close down the SSL/TLS encryption layer, allowing the underlying stream to be used for unencrypted communication.
You almost certainly don’t need this.
Returns: A pair (transport_stream, trailing_bytes)
, wheretransport_stream
is the underlying transport stream, andtrailing_bytes
is a byte string. SinceSSLStream
doesn’t necessarily know where the end of the encrypted data will be, it can happen that it accidentally reads too much from the underlying stream.trailing_bytes
contains this extra data; you should process it as if it was returned from a call totransport_stream.receive_some(...)
.
-
await
wait_send_all_might_not_block
()¶
And if you’re implementing a server, you can use SSLListener
:
-
class
trio.ssl.
SSLListener
(transport_listener, ssl_context, *, https_compatible=False, max_refill_bytes=32768)¶ Bases:
trio.abc.Listener
A
Listener
for SSL/TLS-encrypted servers.SSLListener
wraps around another Listener, and converts all incoming connections to encrypted connections by wrapping them in aSSLStream
.Parameters: - transport_listener (Listener) – The listener whose incoming
connections will be wrapped in
SSLStream
. - ssl_context (SSLContext) – The
SSLContext
that will be used for incoming connections. - https_compatible (bool) – Passed on to
SSLStream
. - max_refill_bytes (int) – Passed on to
SSLStream
.
-
transport_listener
¶ The underlying listener that was passed to
__init__
.Type: trio.abc.Listener
-
await
accept
()¶ Accept the next connection and wrap it in an
SSLStream
.See
trio.abc.Listener.accept()
for details.
-
await
aclose
()¶ Close the transport listener.
- transport_listener (Listener) – The listener whose incoming
connections will be wrapped in
Low-level networking with trio.socket
¶
The 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
to stick to the high-level API described above. If you want to use
UDP, or exotic address families like AF_BLUETOOTH
, or otherwise
get direct access to all the quirky bits of your system’s networking
API, then you’re in the right place.
Top-level exports¶
Generally, the API exposed by trio.socket
mirrors that of the
standard library socket
module. Most constants (like
SOL_SOCKET
) and simple utilities (like inet_aton()
)
are simply re-exported unchanged. But there are also some differences,
which are described here.
First, 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:
-
trio.socket.
socket
(family=<AddressFamily.AF_INET: 2>, type=<SocketKind.SOCK_STREAM: 1>, proto=0, fileno=None)¶ Create a new trio socket, like
socket.socket()
.This function’s behavior can be customized using
set_custom_socket_factory()
.
-
trio.socket.
socketpair
(family=None, type=<SocketKind.SOCK_STREAM: 1>, proto=0)¶ Like
socket.socketpair()
, but returns a pair of trio socket objects.
-
trio.socket.
fromfd
(fd, family, type, proto=0)¶ Like
socket.fromfd()
, but returns a trio socket object.
Like
socket.fromshare()
, but returns a trio socket object.
In addition, there is a new function to directly convert a standard library socket into a trio socket:
-
trio.socket.
from_stdlib_socket
(sock)¶ Convert a standard library
socket.socket()
object into a trio socket object.
Unlike socket.socket()
, trio.socket.socket()
is a
function, not a class; if you want to check whether an object is a
trio socket, use isinstance(obj, trio.socket.SocketType)
.
For name lookup, Trio provides the standard functions, but with some changes:
-
await
trio.socket.
getaddrinfo
(host, port, family=0, type=0, proto=0, flags=0)¶ Look up a numeric address given a name.
Arguments and return values are identical to
socket.getaddrinfo()
, except that this version is async.Also,
trio.socket.getaddrinfo()
correctly uses IDNA 2008 to process non-ASCII domain names. (socket.getaddrinfo()
uses IDNA 2003, which can give the wrong result in some cases and cause you to connect to a different host than the one you intended; see bpo-17305.)This function’s behavior can be customized using
set_custom_hostname_resolver()
.
-
await
trio.socket.
getnameinfo
(sockaddr, flags)¶ Look up a name given a numeric address.
Arguments and return values are identical to
socket.getnameinfo()
, except that this version is async.This function’s behavior can be customized using
set_custom_hostname_resolver()
.
-
await
trio.socket.
getprotobyname
(name)¶ Look up a protocol number by name. (Rarely used.)
Like
socket.getprotobyname()
, but async.
Trio intentionally DOES NOT include some obsolete, redundant, or broken features:
gethostbyname()
,gethostbyname_ex()
,gethostbyaddr()
: obsolete; usegetaddrinfo()
andgetnameinfo()
instead.getservbyport()
: obsolete and buggy; instead, do:_, service_name = await getnameinfo((127.0.0.1, port), NI_NUMERICHOST))
getservbyname()
: obsolete and buggy; instead, do:await getaddrinfo(None, service_name)
getfqdn()
: obsolete; usegetaddrinfo()
with theAI_CANONNAME
flag.getdefaulttimeout()
,setdefaulttimeout()
: instead, use trio’s standard support for Cancellation and timeouts.On Windows,
SO_REUSEADDR
is not exported, because it’s a trap: the name is the same as UnixSO_REUSEADDR
, but the semantics are different and extremely broken. In the very rare cases where you actually wantSO_REUSEADDR
on Windows, then it can still be accessed from the standard library’ssocket
module.
Socket objects¶
-
class
trio.socket.
SocketType
¶ Note
trio.socket.SocketType
is an abstract class and cannot be instantiated directly; you get concrete socket objects by calling constructors liketrio.socket.socket()
. However, you can use it to check if an object is a Trio socket viaisinstance(obj, trio.socket.SocketType)
.Trio socket objects are overall very similar to the standard library socket objects, with a few important differences:
First, and most obviously, everything is made “trio-style”: blocking methods become async methods, and the following attributes are not supported:
setblocking()
: trio sockets always act like blocking sockets; if you need to read/write from multiple sockets at once, then create multiple tasks.settimeout()
: see Cancellation and timeouts instead.makefile()
: Python’s file-like API is synchronous, so it can’t be implemented on top of an async socket.sendall()
: Could be supported, but you’re better off using the higher-levelSocketStream
, and specifically itssend_all()
method, which also does additional error checking.
In addition, the following methods are similar to the equivalents in
socket.socket()
, but have some trio-specific quirks:-
await
connect
()¶ Connect the socket to a remote address.
Similar to
socket.socket.connect()
, except async.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
connect()
is cancelled, and is unable to abort the connection attempt, then it will:- forcibly close the socket to prevent accidental re-use
- raise
Cancelled
.
tl;dr: if
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.
-
sendfile
()¶
We also keep track of an extra bit of state, because it turns out to be useful for
trio.SocketStream
:-
did_shutdown_SHUT_WR
¶ This
bool
attribute is True if you’ve calledsock.shutdown(SHUT_WR)
orsock.shutdown(SHUT_RDWR)
, and False otherwise.
The following methods are identical to their equivalents in
socket.socket()
, except async, and the ones that take address arguments require pre-resolved addresses:accept()
recv()
recv_into()
recvfrom()
recvfrom_into()
recvmsg()
(if available)recvmsg_into()
(if available)send()
sendto()
sendmsg()
(if available)
All methods and attributes not mentioned above are identical to their equivalents in
socket.socket()
:
Asynchronous filesystem I/O¶
Trio provides built-in facilities for performing asynchronous filesystem operations like reading or renaming a file. Generally, we recommend that you use these instead of Python’s normal synchronous file APIs. But the tradeoffs here are somewhat subtle: sometimes people switch to async I/O, and then they’re surprised and confused when they find it doesn’t speed up their program. The next section explains the theory behind async file I/O, to help you better understand your code’s behavior. Or, if you just want to get started, you can jump down to the API overview.
Background: Why is async file I/O useful? The answer may surprise you¶
Many people expect that switching to from synchronous file I/O to async file I/O will always make their program faster. This is not true! If we just look at total throughput, then async file I/O might be faster, slower, or about the same, and it depends in a complicated way on things like your exact patterns of disk access, or how much RAM you have. The main motivation for async file I/O is not to improve throughput, but to reduce the frequency of latency glitches.
To understand why, you need to know two things.
First, right now no mainstream operating system offers a generic,
reliable, native API for async file or filesystem operations, so we
have to fake it by using threads (specifically,
run_sync_in_worker_thread()
). This is cheap but isn’t free: on a
typical PC, dispatching to a worker thread adds something like ~100 µs
of overhead to each operation. (“µs” is pronounced “microseconds”, and
there are 1,000,000 µs in a second. Note that all the numbers here are
going to be rough orders of magnitude to give you a sense of scale; if
you need precise numbers for your environment, measure!)
And second, the cost of a disk operation is incredibly
bimodal. Sometimes, the data you need is already cached in RAM, and
then accessing it is very, very fast – calling io.FileIO
’s
read
method on a cached file takes on the order of ~1 µs. But when
the data isn’t cached, then accessing it is much, much slower: the
average is ~100 µs for SSDs and ~10,000 µs for spinning disks, and if
you look at tail latencies then for both types of storage you’ll see
cases where occasionally some operation will be 10x or 100x slower
than average. And that’s assuming your program is the only thing
trying to use that disk – if you’re on some oversold cloud VM fighting
for I/O with other tenants then who knows what will happen. And some
operations can require multiple disk accesses.
Putting these together: if your data is in RAM then it should be clear that using a thread is a terrible idea – if you add 100 µs of overhead to a 1 µs operation, then that’s a 100x slowdown! On the other hand, if your data’s on a spinning disk, then using a thread is great – instead of blocking the main thread and all tasks for 10,000 µs, we only block them for 100 µs and can spend the rest of that time running other tasks to get useful work done, which can effectively be a 100x speedup.
But here’s the problem: for any individual I/O operation, there’s no way to know in advance whether it’s going to be one of the fast ones or one of the slow ones, so you can’t pick and choose. When you switch to async file I/O, it makes all the fast operations slower, and all the slow operations faster. Is that a win? In terms of overall speed, it’s hard to say: it depends what kind of disks you’re using and your kernel’s disk cache hit rate, which in turn depends on your file access patterns, how much spare RAM you have, the load on your service, … all kinds of things. If the answer is important to you, then there’s no substitute for measuring your code’s actual behavior in your actual deployment environment. But what we can say is that async disk I/O makes performance much more predictable across a wider range of runtime conditions.
If you’re not sure what to do, then we recommend that you use async disk I/O by default, because it makes your code more robust when conditions are bad, especially with regards to tail latencies; this improves the chances that what your users see matches what you saw in testing. Blocking the main thread stops all tasks from running for that time. 10,000 µs is 10 ms, and it doesn’t take many 10 ms glitches to start adding up to real money; async disk I/O can help prevent those. Just don’t expect it to be magic, and be aware of the tradeoffs.
API overview¶
If you want to perform general filesystem operations like creating and
listing directories, renaming files, or checking file metadata – or if
you just want a friendly way to work with filesystem paths – then you
want trio.Path
. It’s an asyncified replacement for the
standard library’s pathlib.Path
, and provides the same
comprehensive set of operations.
For reading and writing to files and file-like objects, Trio also
provides a mechanism for wrapping any synchronous file-like object
into an asynchronous interface. If you have a trio.Path
object you can get one of these by calling its open()
method; or if you know the file’s name you can open it directly with
trio.open_file()
. Alternatively, if you already have an open
file-like object, you can wrap it with trio.wrap_file()
– one
case where this is especially useful is to wrap io.BytesIO
or
io.StringIO
when writing tests.
Asynchronous path objects¶
-
class
trio.
Path
(*args)¶ A
pathlib.Path
wrapper that executes blocking methods intrio.run_sync_in_worker_thread()
.-
as_posix
()¶ Return the string representation of the path with forward (/) slashes.
-
as_uri
()¶ Return the path as a ‘file’ URI.
-
classmethod
cwd
()¶ Return a new path pointing to the current working directory (as returned by os.getcwd()).
-
await
expanduser
(*args, **kwargs)¶ Like
expanduser()
, but async.
-
classmethod
home
()¶ Return a new path pointing to the user’s home directory (as returned by os.path.expanduser(‘~’)).
-
is_absolute
()¶ True if the path is absolute (has both a root and, if applicable, a drive).
-
await
is_block_device
(*args, **kwargs)¶ Like
is_block_device()
, but async.
-
await
is_char_device
(*args, **kwargs)¶ Like
is_char_device()
, but async.
-
is_reserved
()¶ Return True if the path contains one of the special names reserved by the system, if any.
-
await
is_socket
(*args, **kwargs)¶ Like
is_socket()
, but async.
-
await
is_symlink
(*args, **kwargs)¶ Like
is_symlink()
, but async.
-
await
iterdir
()¶ Like
pathlib.Path.iterdir()
, but async.This is an async method that returns a synchronous iterator, so you use it like:
for subpath in await mypath.iterdir(): ...
Note that it actually loads the whole directory list into memory immediately, during the initial call. (See issue #501 for discussion.)
-
joinpath
(*args)¶ Combine this path with one or several arguments, and return a new path representing either a subpath (if all arguments are relative paths) or a totally different path (if one of the arguments is anchored).
-
match
(path_pattern)¶ Return True if this path matches the given pattern.
-
await
open
(mode='r', buffering=-1, encoding=None, errors=None, newline=None)¶ Open the file pointed by this path and return a file object, as the built-in open() function does.
-
await
read_bytes
(*args, **kwargs)¶ Like
read_bytes()
, but async.
-
await
read_text
(*args, **kwargs)¶ Like
read_text()
, but async.
-
relative_to
(*other)¶ Return the relative path to another path identified by the passed arguments. If the operation is not possible (because this is not a subpath of the other path), raise ValueError.
-
await
samefile
(*args, **kwargs)¶ Like
samefile()
, but async.
-
await
symlink_to
(*args, **kwargs)¶ Like
symlink_to()
, but async.
-
with_name
(name)¶ Return a new path with the file name changed.
-
with_suffix
(suffix)¶ Return a new path with the file suffix changed (or added, if none).
-
await
write_bytes
(*args, **kwargs)¶ Like
write_bytes()
, but async.
-
await
write_text
(*args, **kwargs)¶ Like
write_text()
, but async.
-
Asynchronous file objects¶
-
await
trio.
open_file
(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)¶ Asynchronous version of
io.open()
.Returns: An asynchronous file object Example:
async with await trio.open_file(filename) as f: async for line in f: pass assert f.closed
See also
-
trio.
wrap_file
(file)¶ This wraps any file object in a wrapper that provides an asynchronous file object interface.
Parameters: file – a file object Returns: An asynchronous file object that wraps file Example:
async_file = trio.wrap_file(StringIO('asdf')) assert await async_file.read() == 'asdf'
-
Asynchronous file interface
¶ Trio’s asynchronous file objects have an interface that automatically adapts to the object being wrapped. Intuitively, you can mostly treat them like a regular file object, except adding an
await
in front of any of methods that do I/O. The definition of file object is a little vague in Python though, so here are the details:- Synchronous attributes/methods: if any of the following
attributes or methods are present, then they’re re-exported
unchanged:
closed
,encoding
,errors
,fileno
,isatty
,newlines
,readable
,seekable
,writable
,buffer
,raw
,line_buffering
,closefd
,name
,mode
,getvalue
,getbuffer
. - Async methods: if any of the following methods are present, then
they’re re-exported as an async method:
flush
,read
,read1
,readall
,readinto
,readline
,readlines
,seek
,tell
,truncate
,write
,writelines
,readinto1
,peek
,detach
.
Special notes:
Async file objects implement trio’s
AsyncResource
interface: you close them by callingaclose()
instead ofclose
(!!), and they can be used as async context managers. Like allaclose()
methods, theaclose
method on async file objects is guaranteed to close the file before returning, even if it is cancelled or otherwise raises an error.Using the same async file object from multiple tasks simultaneously: because the async methods on async file objects are implemented using threads, it’s only safe to call two of them at the same time from different tasks IF the underlying synchronous file object is thread-safe. You should consult the documentation for the object you’re wrapping. For objects returned from
trio.open_file()
ortrio.Path.open()
, it depends on whether you open the file in binary mode or text mode: binary mode files are task-safe/thread-safe, text mode files are not.Async file objects can be used as async iterators to iterate over the lines of the file:
async with await trio.open_file(...) as f: async for line in f: print(line)
The
detach
method, if present, returns an async file object.
This should include all the attributes exposed by classes in
io
. But if you’re wrapping an object that has other attributes that aren’t on the list above, then you can access them via the.wrapped
attribute:-
wrapped
¶ The underlying synchronous file object.
- Synchronous attributes/methods: if any of the following
attributes or methods are present, then they’re re-exported
unchanged:
Subprocesses¶
Signals¶
-
with
trio.
open_signal_receiver
(*signals) as signal_aiter¶ A context manager for catching signals.
Entering this context manager starts listening for the given signals and returns an async iterator; exiting the context manager stops listening.
The async iterator blocks until a signal arrives, and then yields it.
Note that if you leave the
with
block while the iterator has unextracted signals still pending inside it, then they will be re-delivered using Python’s regular signal handling logic. This avoids a race condition when signals arrives just before we exit thewith
block.Parameters: signals – the signals to listen for. Raises: RuntimeError
– if you try to use this anywhere except Python’s main thread. (This is a Python limitation.)Example
A common convention for Unix daemons is that they should reload their configuration when they receive a
SIGHUP
. Here’s a sketch of what that might look like usingopen_signal_receiver()
:with trio.open_signal_receiver(signal.SIGHUP) as signal_aiter: async for signum in signal_aiter: assert signum == signal.SIGHUP reload_configuration()