Host#
A Host manages a collection of spawned procs and provides bidirectional routing between them. It serves as the entry point for external connections and coordinates message delivery across local and remote actors.
Overview#
The Host struct maintains two key channel endpoints:
Frontend address: accepts connections from external clients
Backend address: receives messages from spawned procs
Both endpoints feed into a unified routing layer that can deliver messages to either the service proc (running within the host) or to spawned procs.
┌────────────┐
┌───▶ proc *,1 │
│ #1└────────────┘
│
┌──────────┐ │ ┌────────────┐
│ Host │◀───┼───▶ proc *,2 │
*└──────────┘# │ #2└────────────┘
│
│ ┌────────────┐
└───▶ proc *,3 │
#3└────────────┘
Where:
*is the host’s frontend address (frontend_addr)#is the host’s backend address (backend_addr)#1,#2,#3are the per-proc backend channelsEach proc is direct-addressed via the host - its id is “proc at
*namedN”
Structure#
pub struct Host<M> {
procs: HashSet<String>,
frontend_addr: ChannelAddr,
backend_addr: ChannelAddr,
router: DialMailboxRouter,
manager: M,
service_proc: Proc,
local_proc: Proc,
frontend_rx: Option<ChannelRx<MessageEnvelope>>,
}
Fields:
procs: Stores proc names to avoid creating duplicatesfrontend_addr: The address external clients connect tobackend_addr: The address spawned procs use to send messages back to the hostrouter: ADialMailboxRouterfor prefix-based routing to spawned procsmanager: AProcManagerimplementation that handles proc lifecycleservice_proc: The host’s local proc for system-level actorslocal_proc: The host’s local proc for user-level actorsfrontend_rx: Channel receiver for external connections (consumed during startup)
Creating a Host#
The Host::new constructor takes a ProcManager and a channel address to serve:
impl<M: ProcManager> Host<M> {
pub async fn new(manager: M, addr: ChannelAddr) -> Result<Self, HostError> {
let (frontend_addr, frontend_rx) = channel::serve(addr)?;
let (backend_addr, backend_rx) = channel::serve(
ChannelAddr::any(manager.transport())
)?;
let router = DialMailboxRouter::new();
let service_proc_id = ProcId::Direct(
frontend_addr.clone(),
"service".to_string()
);
let service_proc = Proc::new(service_proc_id.clone(), router.boxed());
let local_proc_id = ProcId::Direct(
frontend_addr.clone(),
"local".to_string()
);
let local_proc = Proc::new(local_proc_id.clone(), router.boxed());
let host = Host {
procs: HashSet::new(),
frontend_addr,
backend_addr,
router,
manager,
service_proc,
local_proc,
frontend_rx: Some(frontend_rx),
};
let _backend_handle = host.forwarder().serve(backend_rx);
Ok(host)
}
}
Understanding channel::serve()#
channel::serve() is the universal “bind and listen” operation across different transport types. It:
Takes a
ChannelAddr(which can be a wildcard likeChannelAddr::any())Binds a server/listener on that address
Returns a tuple of:
The actual bound address (resolved from wildcards)
A receiver (
ChannelRx<MessageEnvelope>) for incoming messages
This is why both calls in Host::new capture the returned address:
let (frontend_addr, frontend_rx) = channel::serve(addr)?;
let (backend_addr, backend_rx) = channel::serve(ChannelAddr::any(manager.transport()))?;
The returned address is the actual bound address you can give to others to connect to. For example, when you pass ChannelAddr::Tcp(127.0.0.1:0):
Input: “bind to localhost on any available port”
Output:
(ChannelAddr::Tcp(127.0.0.1:54321), rx)- the OS-assigned port
See Channel Addresses and Transmits and Receives for more on channel semantics.
The Service Proc and Local Proc#
The host creates two procs identified by ProcId::Direct:
Service Proc:
let service_proc_id = ProcId::Direct(
frontend_addr.clone(),
"service".to_string()
);
let service_proc = Proc::new(service_proc_id, router.boxed());
Local Proc:
let local_proc_id = ProcId::Direct(
frontend_addr.clone(),
"local".to_string()
);
let local_proc = Proc::new(local_proc_id, router.boxed());
Both procs:
Live within the host process
Use
ProcId::Direct(frontend_addr, name)as their identityForward outbound messages through the
DialMailboxRouterThe service proc hosts system-level actors that manage proc lifecycle and coordination
The local proc hosts user-level actors
See ProcId variants for the distinction between Ranked and Direct addressing.
Routing Architecture#
The host implements bidirectional routing using a specialized ProcOrDial router (see ProcOrDial Router). Both the frontend and backend receivers are served by this router:
Backend receiver (from spawned procs):
let _backend_handle = host.forwarder().serve(backend_rx);
Frontend receiver (from external clients):
Some(self.forwarder().serve(self.frontend_rx.take()?))
Complete Routing Flow#
frontend_rx (external connections) ──┐
├──> serve() ──> ProcOrDial ──┬──> service proc
backend_rx (from spawned procs) ──┘ ├──> local proc
└──> DialMailboxRouter
│
└──> looks up proc by name
└──> dials backend addr
Both receivers feed into the same ProcOrDial router, creating bidirectional routing:
Inbound (frontend): External → ProcOrDial → service proc or local proc or spawned proc
Inbound (backend): Spawned procs → ProcOrDial → service proc or local proc or other spawned procs
Outbound (from service proc or local proc):
service_proc.forwarder/local_proc.forwarder= DialMailboxRouter → spawned procs
See MailboxServer::serve() for how receivers are bridged to routers.
Channel Receivers#
The ChannelRx<M> receiver returned from channel::serve() implements the Rx<M> trait:
trait Rx<M: RemoteMessage> {
async fn recv(&mut self) -> Result<M, ChannelError>;
fn addr(&self) -> ChannelAddr;
}
It’s a stream of incoming messages of type M. In the host context, M = MessageEnvelope, so it receives actor messages from the network.
How the host uses receivers:
Frontend: Serves the user-provided
addr→ receives messages from external connections viafrontend_rxBackend: Serves a wildcard backend address → receives messages from spawned procs via
backend_rx
Both are consumed by calling .serve() on the ProcOrDial forwarder, which bridges the channel receivers to the mailbox routing system.
Next Steps#
See ProcOrDial Router for the routing implementation
See Routers for
DialMailboxRouterdetailsSee Proc for how procs integrate with routers, including the local proc bypass optimization