typed_actor and named_genserver land as written (the target-ergonomics spec): identity-bound Pid<A> vs durable Name<M>, and a gen_server addressed by a durable ServerName. worker_pool is reworked from the spec into a self-draining program: workers retire on a sentinel and report their tally, and the dispatcher waits the pool out — so it terminates on its own rather than leaning on root-exit teardown, while still exercising the full typed surface (spawn_addr / join / pick_as / members_as / dispatch) alongside the untyped escape hatch (members / pick + send_dyn). All three build unchanged-against-spec (typed_actor, named_genserver) and run to completion.
69 lines
2.1 KiB
Rust
69 lines
2.1 KiB
Rust
//! Addressing a gen_server by a durable name.
|
|
//!
|
|
//! A gen_server is multi-message (call / cast over one inbox), so it is named
|
|
//! by the *server* type rather than by a single message type. Registering it
|
|
//! under a [`ServerName`] lets clients `call` and `cast` by name, resolving on
|
|
//! every use — so the address keeps working across a supervised restart, with
|
|
//! no stale [`ServerRef`] to refresh.
|
|
|
|
use smarm::{call, cast, run, whereis_server, GenServer, ServerBuilder, ServerName, ServerRef};
|
|
|
|
/// A counter server: synchronous `Get`, asynchronous `Inc` / `Add`.
|
|
struct Counter {
|
|
n: u64,
|
|
}
|
|
enum Query {
|
|
Get,
|
|
}
|
|
enum Update {
|
|
Inc,
|
|
Add(u64),
|
|
}
|
|
impl GenServer for Counter {
|
|
type Call = Query;
|
|
type Reply = u64;
|
|
type Cast = Update;
|
|
type Info = ();
|
|
|
|
fn handle_call(&mut self, q: Query) -> u64 {
|
|
match q {
|
|
Query::Get => self.n,
|
|
}
|
|
}
|
|
fn handle_cast(&mut self, u: Update) {
|
|
match u {
|
|
Update::Inc => self.n += 1,
|
|
Update::Add(k) => self.n += k,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A durable name typed by the server, so by-name `call` / `cast` check against
|
|
/// `Counter`'s `Call` / `Cast` / `Reply`.
|
|
const COUNTER: ServerName<Counter> = ServerName::new("counter");
|
|
|
|
fn main() {
|
|
run(|| {
|
|
// Start the server and bind its name in one step. A named start is
|
|
// fallible: the name may already be held by another live server.
|
|
ServerBuilder::new(Counter { n: 0 })
|
|
.named(COUNTER)
|
|
.start()
|
|
.unwrap();
|
|
|
|
// Address it purely by name. Each call/cast resolves through the
|
|
// registry, so a server restarted under the same name is reached
|
|
// transparently.
|
|
cast(COUNTER, Update::Inc).unwrap();
|
|
cast(COUNTER, Update::Add(41)).unwrap();
|
|
assert_eq!(call(COUNTER, Query::Get).unwrap(), 42);
|
|
|
|
// When you want a handle to hold or pass on rather than resolve per
|
|
// call, recover a typed `ServerRef` from the name.
|
|
let svc: Option<ServerRef<Counter>> = whereis_server(COUNTER);
|
|
if let Some(svc) = svc {
|
|
let _ = svc.call(Query::Get);
|
|
}
|
|
});
|
|
}
|