valis / Reference / API reference
Backends - API reference
Exported surface for the backends subsystem. Part of the API reference.
Package valis/src/backends/epoll
Classes
epoll-datagram-source
UDP datagram source. The fd IS the connection's identity (there is no accept step); each receive-from produces a datagram-connection carrying the payload, the peer, and the recovered destination port. Datagrams have no stream semantics, so no stream view is offered on this connection — the datagram-connection class signals stream-not-available if connection-stream is called.
epoll-mux
Concrete multiplexer backed by iolib's event-base (epoll on Linux). The class makes the backend seam operational: every iolib type stays inside this file, and the abstract multiplexer generics specialise here to drive the loop, route readiness, and translate accept(2) and receive-from into valis connections.
epoll-stream-source
TCP stream source. source-ready accepts a non-blocking connection, recovers the original destination port on the accepted (NOT the listening) socket, builds a tcp-connection, and hands it to the executor — all before returning to the event loop.
Conditions
fd-already-registered
Signalled when register-source is called for a fd already present in the mux's sources table. iolib's set-io-handler raises 'FD already monitored' in that situation; this condition catches it earlier with a more useful message and prevents the registry from silently losing a source.
Generic functions
fd-already-registered-fd
(fd-already-registered-fd condition)
Undocumented: this exported symbol needs a docstring.
Functions
close-listening-fd
(close-listening-fd fd)
Release a descriptor minted by MAKE-LISTENING-FD back to the kernel. Its exact counterpart: MAKE-LISTENING-FD hands back a bare integer, so the release is the close(2) syscall, not CL:CLOSE — the latter is a stream operation and signals a TYPE-ERROR on an integer, leaving the descriptor open. Naming it here keeps iolib confined to this backend, so a consumer that mints a LISTEN fd can also release one without importing iolib and without a shadowed CLOSE in its own package.
make-connection-non-blocking
(make-connection-non-blocking connection)
Put CONNECTION's descriptor in non-blocking mode and answer CONNECTION.
The call is made rather than assumed, and that is why it exists at all. A reader driven by readiness notification must never park inside the socket library's own poll loop; the library sets this mode itself today, but a choice somebody else made can be withdrawn in a release, and the loop thread would then block on one slow peer with nothing to say why.
A connection driven through these three primitives must never have its buffered stream view taken, before or after. That view reads ahead into a userspace buffer, and bytes sitting there have already left the kernel, so readiness notification cannot see them. The consequence is a connection that hangs with its data already delivered.
make-datagram-source
(make-datagram-source &key (host "127.0.0.1") port executor)
Construct a UDP datagram source bound to HOST:PORT. The bind step is mandatory (an unbound UDP socket has ephemeral port 0 and the registry has nothing to route on). Captures the bound port via local-port once at construction and caches it on the source so source-ready does not re-syscall on every packet, and so a mid-teardown getsockname cannot crash the loop thread. Returns an epoll-datagram-source ready to register-source.
make-datagram-source-from-fd
(make-datagram-source-from-fd &key fd executor)
Construct a UDP datagram source from an inherited, ALREADY-BOUND file descriptor — the steered-fd analogue of make-datagram-source. There is no make-socket / bind-address step: the fd arrives already bound to its port (an eBPF-steered socket, or one a co-located privileged agent bound and handed off over SCMRIGHTS), and valis never binds a designated low port itself. The address family is recovered from the descriptor, and dest-port is recovered once via local-port and cached on the source exactly as the bind path caches it.
iolib's public make-socket-from-fd cannot wrap a datagram fd — it forwards stream-only initargs the datagram socket class rejects — so the descriptor is adopted through the lower-level create-socket with only the datagram-valid initargs. Keeping every iolib symbol here preserves the seam invariant: the edge controller builds a steered datagram source without naming iolib.
make-epoll-mux
(make-epoll-mux)
Construct a fresh epoll-mux with an empty sources table, a new iolib event-base, and a registered self-pipe wakeup. The event-base instantiation selects the default multiplexer for the platform (epoll on Linux); valis treats that choice as opaque. The self-pipe's read end is registered with the event-base so a stop-loop write to the corresponding write end forces epollwait to return on demand from another thread.
make-listening-fd
(make-listening-fd &key (host "127.0.0.1") port (backlog +default-backlog+))
Bind and listen a passive AFINET stream socket on HOST:PORT and return its raw descriptor as an integer — valis's production helper for minting a designated-port LISTEN fd it owns. Lifts the exact bind-address + listen-on steps make-stream-source uses (SOREUSEADDR so a quick teardown cycle does not fight TIMEWAIT), then hands back a bare descriptor the caller owns: the fd is DUP'd out of the iolib socket because closing that socket closes the descriptor it holds, and this function closes it before returning. Handed the socket's own descriptor, a caller would find it already shut. A PORT of 0 binds an ephemeral port (the kernel picks it); the caller recovers the actual port by getsockname on the fd when it needs it.
This is the src/ equivalent of the test-only make-listening-socket: valis is the only party that binds and listens (the steered-fd handoff consent model), and the resident boot pushes this descriptor to the host agent while keeping its own copy to wrap as the steered source. Keeping the iolib names here preserves the seam invariant — this backend is the one file that names iolib for the source path — so a consumer mints a LISTEN fd without importing iolib.
make-stream-source
(make-stream-source &key (host "127.0.0.1") port executor (backlog +default-backlog+))
Construct a TCP stream source bound to HOST:PORT. HOST is a dotted-quad
string ("127.0.0.1" by default — the listener binds loopback only
until real authentication lands); PORT is an (unsigned-byte 16);
BACKLOG is the listen(2) queue
depth, defaulting to default-backlog (128, matching SOMAXCONN on
Linux). SOREUSEADDR is set on the passive socket so a quick
stop-listener / start-listener cycle does not fight TIMEWAIT
(close-source releases the fd; SOREUSEADDR lets the next bind on
the same port succeed even before the kernel finishes its TCP-state
timer). Returns an epoll-stream-source ready to register-source.
make-stream-source-from-fd
(make-stream-source-from-fd &key fd executor)
Construct a TCP stream source from an inherited, ALREADY-LISTENING file descriptor — the steered-fd path. Unlike make-stream-source there is no make-socket / bind-address / listen-on / SOREUSEADDR step: the fd arrives already in LISTEN state (an eBPF-steered socket, or a socket a co-located agent created and handed off), and binding it a second time would signal. make-socket-from-fd wraps the descriptor as a :passive iolib socket, recovering its address family and type by getsockname on the fd. The resulting epoll-stream-source is the SAME class the bind path builds, so source-ready's destination-port recovery — local-port on the ACCEPTED client, never the listening socket — is inherited unchanged and the steered four-tuple survives exactly as the loopback path proves it.
Keeping make-socket-from-fd here preserves the seam invariant: this is the one file that names an iolib symbol, so the edge controller can build a steered source without importing iolib.
peer-address-octets
(peer-address-octets peer-address)
Convert an iolib peer address (as returned by receive-from or accept-connection) into (values RAW-OCTETS FAMILY-KEYWORD): a (simple-array (unsigned-byte 8)) of 4 octets with :ipv4, or 16 octets with :ipv6, the shape runciter's make-client-address keys RRL on. iolib yields an IPv6 address as eight 16-bit words, so the IPv6 case is widened to its 16 network-order octets. Keeping this conversion here holds the seam invariant — a caller obtains RRL-ready octets without naming an iolib symbol.
peer-address-string
(peer-address-string peer-address)
Render an iolib peer address (as returned by receive-from or accept-connection) to its canonical textual form: a dotted-quad for IPv4 ("192.0.2.10"), an RFC 5952 compressed literal for IPv6. This is the address form the transfer allowlist stores and matches a pulling secondary by (a case-folded string compare), so a serving-side authorization check presents the peer in exactly the shape the enrollment recorded. Keeping the rendering here holds the seam invariant: a caller names the peer as text without naming an iolib symbol.
read-available
(read-available connection buffer &key (start 0) end)
Read ONCE from CONNECTION into BUFFER between START and END, answering the octet count read, 0 when the descriptor would block, or :EOF when the peer closed in an orderly way. BUFFER is a simple octet vector.
One syscall per call and the caller loops, which is what bounds a drain by what actually arrived rather than by what a peer might yet send. A descriptor that went away underneath the call answers :EOF rather than signalling: the drain runs inside a readiness callback, where an escaping condition would take the whole event loop with it.
A connection read this way must never have its buffered stream view taken, before or after. That view reads ahead into a userspace buffer, and bytes sitting there have already left the kernel, so readiness notification cannot see them. The consequence is a connection that hangs with its data already delivered.
reply-datagram
(reply-datagram connection octets)
Send OCTETS back to CONNECTION's recorded peer over the datagram source's UDP socket — the send counterpart to source-ready's receive-from, and the one path a UDP answer leaves by. CONNECTION carries the peer address and port receive-from recovered and the source's socket; sending to that exact four-tuple lands the reply at the peer that sent the query and nowhere else. Keeping send-to here holds the seam invariant: a caller replies without naming an iolib symbol. Returns the number of octets sent.
write-available
(write-available connection buffer &key (start 0) end)
Write ONCE from BUFFER between START and END to CONNECTION, answering the octet count that actually left, 0 when the descriptor would block, or :EOF when it went away underneath the call. BUFFER is a simple octet vector.
The count is very often short of what was asked, because a peer that is slow to read fills the descriptor's room and the rest has to wait for writability. A caller that treats the answer as all-or-nothing silently drops the remainder, so the count is the whole contract here.
A connection written this way must never have its buffered stream view taken, before or after. That view holds bytes in a userspace buffer of its own, so two writers would interleave on the wire and neither would know.
Variables
+default-backlog+
Default TCP listen(2) backlog for stream sources. 128 matches the historical Linux SOMAXCONN default and is large enough that a burst of loopback connects through one accept-until-EWOULDBLOCK pass does not lose connections to a too-shallow accept queue. Callers can override per source via make-stream-source's :backlog keyword.