03 / ROUTING
socket
A Listener puts an RpcServer on TCP with the same handler table and the same interceptor chain the in-process transport runs; port 0 asks the system for a free one and bind answers the number it picked, so a test connects to the port that exists rather than to a number it chose. A SocketChannel multiplexes calls as HTTP/2 streams over one connection and speaks h2c to any gRPC peer.
WHERE IT SITS
INTERFACE
struct ListenerstructA server's methods, bound to a gRPC server but not yet listening. Holding the handle separate from the blocking serve is what gives a process a way to stop: stop is a graceful drain, so a test can start a listener, call it, and shut it down without cancelling a task that is parked in accept.
fn Listener::new(server : @zrpc.RpcServer) -> ListenerfnA listener for server's methods. Every registered method is registered with the gRPC server under its own path, and each one dispatches through RpcServer::invoke — so a call arriving on the socket passes the same interceptor chain a call made in process does, including authentication, shedding and the deadline.
async fn Listener::bind( self : Listener, host~ : String = "127.0.0.1", port~ : Int32 = 12001) -> Int32itemBind the listening socket now and report the port it got, so a caller reaches the server before any accept loop has started — and so port 0 answers the number the system chose instead of the 0 it was handed. Connections queue from the moment this returns, so a client may connect at once; serve then accepts on the socket this opened instead of binding a second one. A listener that already holds a socket keeps it, and reports the port it has.
async fn Listener::serve( self : Listener, host~ : String = "127.0.0.1", port~ : Int32 = 12001) -> UnititemListen on host:port and serve until stop drains the listener or the running task is cancelled. Port 0 leaves the choice to the system; bind reports the number it picked, and bound_port answers it from another task. A socket that bind already opened is served as it is.
async fn Listener::stop(self : Listener, grace~ : Int32 = 10_000) -> UnititemStop accepting, let the calls already running finish, and tell every client which streams were accepted so it can reconnect elsewhere. grace is how long a quiet client is waited on before the connection is given up.
async fn serve( server : @zrpc.RpcServer, host~ : String = "127.0.0.1", port~ : Int32 = 12001) -> UnititemServe server on host:port for as long as the running task lives. A process that wants to shut down on its own terms builds a Listener instead and calls stop.
struct SocketChannelstructA channel to a zRPC server over a socket. The connection is opened once and multiplexed: calls are separate HTTP/2 streams, so they do not queue behind each other, and the HPACK state is the channel's for its whole life.
async fn SocketChannel::connect( host : String, port : Int32) -> SocketChannelitemOpen a channel to host:port. The connection is a real one: the preface and the client SETTINGS go out here, and the server's SETTINGS are read on the first call.
async fn SocketChannel::call( self : SocketChannel, path : String, request : Bytes, deadline_ms? : Int32) -> Result[Bytes, @moonrpc.Status]itemInvoke a unary method at path with request as its message payload. request and the returned reply are bare message bytes: the five-octet gRPC length prefix is applied and stripped by the transport, so a handler and a caller on either side of this call see the same bytes they would in process.
async fn SocketChannel::call_detailed( self : SocketChannel, path : String, request : Bytes, deadline_ms? : Int32) -> (Result[Bytes, @moonrpc.Status], String)itemSend one call and report what the peer said about it, status and message both. A caller that wants to log why a call failed reads the message here rather than reconstructing it from the status code.
fn SocketChannel::close(self : SocketChannel) -> UnitfnClose the connection. A call already in flight on another stream is not cancelled by this; the peer sees a GOAWAY and finishes what it started.
fn Listener::port(self : Listener) -> Int32?fnThe port this listener is bound to, or None before it has bound one. A listener asked for port 0 gets the port the system chose, not the 0 it was given, so this is the only honest way to learn where it listens.
suberror TimeoutitemA listener that held no socket when the wait ran out.
async fn Listener::bound_port( self : Listener, timeout_ms~ : Int32 = 5_000) -> Int32 raise TimeoutitemWait until the listener holds a listening socket and report the port it bound. This is what makes port 0 usable from a task other than the one serving: the caller does not know the number before the socket exists, and cannot guess it afterwards. A listener this task bound itself has the number already, and bind answers it directly. The wait ends on the bind, not on a poll: the socket releases it the moment it exists, so the answer costs one wakeup. timeout_ms bounds it rather than parking for ever — a serve task that never reached the bind, or one that could not bind at all, raises Timeout.