examples: typed_actor, named_genserver, worker_pool for the new surface
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.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
//! 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user