Networking¶
zuvloop implements the whole of asyncio's networking surface. There is no zuvloop API here — these are asyncio's methods, and this page records what zuvloop does underneath and where it is worth knowing.
Connections¶
The socket is created, resolved and connected in Python; libuv takes the descriptor from there and owns every read and write. That split is deliberate: a connection is established once, and the readable implementation is worth more there than the last microsecond.
create_unix_connection works the same way over AF_UNIX.
Servers¶
server = await loop.create_server(Protocol, "127.0.0.1", 8080)
async with server:
await server.serve_forever()
zuvloop.Server implements asyncio.AbstractServer, including
close_clients() and abort_clients().
create_unix_server accepts cleanup_socket, and honours it the way asyncio
does: the socket file is unlinked at close only if it is still the same file that
was bound, compared by device and inode.
Reading¶
Reads land in a bytes object sized to what the peer has been sending. Below
64 KiB the data is copied out of a shared buffer into an exactly sized object —
an HTTP request is a couple of hundred bytes, and allocating a large object to
shrink it again costs more than the copy. Above that, libuv fills the final
object directly and nothing is copied.
BufferedProtocol is supported and goes one better: libuv reads straight into
the buffer your protocol hands out.
class Protocol(asyncio.BufferedProtocol):
def get_buffer(self, sizehint: int) -> memoryview:
return self._buffer
def buffer_updated(self, nbytes: int) -> None: ...
Writing¶
Writes issued during one turn of the loop are sent together, as a single vectored write. This matters more than it sounds: ASGI and aiohttp both send a response as a header write followed by a body write, so a loop that writes each piece as it arrives spends two syscalls on every response.
Nothing is copied. A write the socket accepts outright never allocates; when it cannot take everything, the queued request holds a buffer view of your memory rather than a copy of it.
/// note | What get_write_buffer_size() reports
Bytes still waiting in the batch count towards the queue, so
get_write_buffer_size() is non-zero between a write() and the end of the
turn. They are not counted as backed up, though: flow control triggers on what
the socket refused, not on what has not been offered to it yet.
///
TLS¶
TLS runs through asyncio's own SSLProtocol, over a native transport. start_tls
works too, including the case where it swaps a plain protocol for a
BufferedProtocol — the transport re-reads which one it has rather than assuming.
Descriptor watching¶
add_reader, add_writer and their remove_ counterparts are implemented with
one uv_poll_t per descriptor, and the sock_* family
(sock_recv, sock_sendall, sock_connect, sock_accept, …) is built on them.
sock_sendfile and sendfile raise NotImplementedError. asyncio falls back to
a read-and-write loop when a loop declines them, so file responses still work.