//! gen_server — synchronous call / asynchronous cast over a single actor. //! //! A thin request-reply layer on top of [`channel`](crate::channel), modelled //! on Erlang's `gen_server`. A *server* is an actor owning a state value that //! implements [`GenServer`]; clients hold a clonable [`ServerRef`] and issue //! [`call`](ServerRef::call) (synchronous, returns a reply) or //! [`cast`](ServerRef::cast) (fire-and-forget). //! //! ## One inbox, many arms //! //! Every call and cast travels the *same* inbox channel as an [`Envelope`] //! (calls and casts are ordered relative to each other, like Erlang); the //! server loop dispatches by variant. A `call` carries a freshly made //! one-shot reply channel; the server sends the reply straight back down it. //! //! Out-of-band messages ride *separate* channels composed at the wait via //! [`select`](crate::channel::select): info channels handed over at start //! ([`ServerBuilder::with_info`]) are dispatched to //! [`handle_info`](GenServer::handle_info). Arm priority is //! **infos before inbox**, in declaration order — a hot inbox cannot starve //! an out-of-band message; conversely a hot info channel CAN starve the //! inbox, deliberately (system-message semantics). An info channel whose //! senders are all gone is silently dropped from the arm set (per the //! closed-arm-is-ready-forever rule on `select`); a closed *inbox* still //! means graceful shutdown. //! //! ## Server death //! //! Detection falls out of channel closure, so no monitor is required: //! - if the server is already gone, its inbox is closed and the `send` in //! `call`/`cast` fails → [`CallError::ServerDown`] / [`CastError::ServerDown`]; //! - if the server dies *after* a call is enqueued but before it replies //! (a handler panic, or a cooperative `request_stop`), the reply sender is //! dropped as the server's stack unwinds, closing the reply channel; the //! parked caller wakes and its `recv` returns `Err` → `ServerDown`. //! //! ## Lifecycle / callbacks //! //! [`GenServer::init`] runs once inside the server actor before the first //! message; [`GenServer::terminate`] runs on the way out. terminate is wired //! through a drop guard, so it fires on *every* exit path — graceful inbox //! close, a handler panic, or a cooperative `request_stop` — not only the clean //! one. Keep it cheap and non-blocking: it may run mid-unwind, and a panic //! inside it during an unwind aborts the process (a double panic). //! //! ## Not here (yet) //! //! No dynamic info subscription: the info-channel set is fixed at start. //! Revisit against a real consumer. (Monitor `Down` forwarding is dynamic — //! see `handle_down` — because monitors are inherently created at runtime.) use crate::channel::{channel, select, Receiver, Selectable, Sender}; use crate::monitor::{Down, Monitor}; use crate::pid::Pid; use crate::scheduler::{spawn, spawn_under}; /// Behaviour for a gen_server: a state value plus call/cast handlers. /// /// `handle_call` and `handle_cast` are required; [`init`](Self::init) and /// [`terminate`](Self::terminate) are optional lifecycle hooks with no-op /// defaults. pub trait GenServer: Send + 'static { /// Synchronous request type (carried by [`ServerRef::call`]). type Call: Send + 'static; /// Reply type returned for a `Call`. type Reply: Send + 'static; /// Asynchronous request type (carried by [`ServerRef::cast`]). type Cast: Send + 'static; /// Out-of-band message type, delivered to [`handle_info`](Self::handle_info) /// from the info channels registered at start /// ([`ServerBuilder::with_info`]). Servers with several out-of-band /// sources enum them up into one `Info`. Use `()` if unused. type Info: Send + 'static; /// Runs once inside the server actor before any message is handled. The /// [`ServerCtx`] is the loop's one runtime hook: clone its [`Watcher`] /// into the state here to be able to [`watch`](Watcher::watch) monitors /// from any later handler. fn init(&mut self, _ctx: &ServerCtx) {} /// Handle a synchronous call and produce the reply sent back to the caller. fn handle_call(&mut self, request: Self::Call) -> Self::Reply; /// Handle a fire-and-forget cast. fn handle_cast(&mut self, request: Self::Cast); /// Handle an out-of-band message from one of the info channels. Default: /// drop it. fn handle_info(&mut self, _info: Self::Info) {} /// Handle a [`Down`] from a monitor handed to the loop via /// [`Watcher::watch`]. Default: drop it. fn handle_down(&mut self, _down: Down) {} /// Runs as the server actor exits, on any exit path (see module docs). fn terminate(&mut self) {} } /// What travels the server's single inbox channel: a synchronous call (with a /// reply sender) or an asynchronous cast. enum Envelope { Call(G::Call, Sender), Cast(G::Cast), } /// A clonable handle to a running server. Cloning yields another sender to the /// same inbox; the server lives until the last `ServerRef` is dropped, at which /// point its inbox closes and the loop exits normally. pub struct ServerRef { tx: Sender>, pid: Pid, } impl Clone for ServerRef { fn clone(&self) -> Self { ServerRef { tx: self.tx.clone(), pid: self.pid } } } /// Returned by [`ServerRef::call`] when the server is no longer reachable. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum CallError { /// The server was already gone, or died before replying. ServerDown, } /// Returned by [`ServerRef::call_timeout`]. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum CallTimeoutError { /// The server was already gone, or died before replying. ServerDown, /// The deadline passed before a reply arrived. The request stays in the /// server's inbox: it will still be *handled*, but the reply is discarded /// (the abandoned reply channel's receiver is dropped, so the server's /// reply send fails harmlessly). Erlang behaves the same way; design /// idempotent calls accordingly. Timeout, } /// Returned by [`ServerRef::cast`] when the server is no longer reachable. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum CastError { /// The server inbox was closed (the server is gone). ServerDown, } impl ServerRef { /// The server actor's pid — usable with `monitor`, `request_stop`, `link`. pub fn pid(&self) -> Pid { self.pid } /// Synchronous request-reply. Blocks (parking the calling actor) until the /// server replies, or returns [`CallError::ServerDown`] if the server is or /// becomes unreachable before a reply arrives. pub fn call(&self, request: G::Call) -> Result { let (reply_tx, reply_rx) = channel::(); self.tx .send(Envelope::Call(request, reply_tx)) .map_err(|_| CallError::ServerDown)?; reply_rx.recv().map_err(|_| CallError::ServerDown) } /// Bounded synchronous request-reply: like [`call`](Self::call), but /// gives up after `timeout`, returning [`CallTimeoutError::Timeout`]. /// /// The roadmap sketched this as monitor + wait-reply-or-Down + demonitor; /// that machinery is unnecessary here because server death is already /// observable on the reply channel itself — the reply sender is dropped /// as the server's stack unwinds, closing the channel and waking the /// parked caller (see the module docs). So a bounded call is exactly /// [`Receiver::recv_timeout`] on the reply channel: `Ok` on a reply, /// `Disconnected` → [`CallTimeoutError::ServerDown`], `Timeout` → /// [`CallTimeoutError::Timeout`]. Nothing is registered, so nothing can /// leak on the timeout path by construction. pub fn call_timeout( &self, request: G::Call, timeout: std::time::Duration, ) -> Result { let (reply_tx, reply_rx) = channel::(); self.tx .send(Envelope::Call(request, reply_tx)) .map_err(|_| CallTimeoutError::ServerDown)?; reply_rx.recv_timeout(timeout).map_err(|e| match e { crate::channel::RecvTimeoutError::Disconnected => CallTimeoutError::ServerDown, crate::channel::RecvTimeoutError::Timeout => CallTimeoutError::Timeout, }) } /// Fire-and-forget request. Returns once the message is enqueued; does not /// wait for the server to handle it. [`CastError::ServerDown`] if the inbox /// is already closed. pub fn cast(&self, request: G::Cast) -> Result<(), CastError> { self.tx .send(Envelope::Cast(request)) .map_err(|_| CastError::ServerDown) } } /// The server loop's runtime hook, passed to [`GenServer::init`]. Currently /// carries only the [`Watcher`]; opaque so fields can grow without breaking. pub struct ServerCtx { watcher: Watcher, } impl ServerCtx { /// A clonable handle to the loop's monitor intake. Store it in the state /// during `init` to watch monitors from later handlers. pub fn watcher(&self) -> Watcher { self.watcher.clone() } /// Shorthand for `ctx.watcher().watch(m)` when watching during `init`. pub fn watch(&self, m: Monitor) { self.watcher.watch(m) } } /// Hands [`Monitor`]s to a server loop; their [`Down`]s are dispatched to /// [`GenServer::handle_down`]. Down arms outrank every other arm (a death /// notice cannot be starved), and a delivered `Down` retires its arm — /// monitors are one-shot. #[derive(Clone)] pub struct Watcher { tx: Sender, } impl Watcher { /// Transfer `m` to the server loop. If the server is already gone the /// monitor is silently dropped (its queued `Down`, if any, with it) — /// there is no loop left to care. pub fn watch(&self, m: Monitor) { let _ = self.tx.send(m); } } /// Configure-then-start construction for servers that need more than a bare /// [`start`]: info channels, a supervisor. Consumed by [`start`](Self::start). /// /// ```ignore /// let server = ServerBuilder::new(state) /// .with_info(events_rx) /// .under(supervisor_pid) /// .start(); /// ``` pub struct ServerBuilder { state: G, infos: Vec>, supervisor: Option, } impl ServerBuilder { pub fn new(state: G) -> Self { ServerBuilder { state, infos: Vec::new(), supervisor: None } } /// Add an out-of-band channel; messages arriving on it are dispatched to /// [`GenServer::handle_info`]. Repeatable; arm priority is call order /// (earlier = higher), and every info channel outranks the inbox. pub fn with_info(mut self, rx: Receiver) -> Self { self.infos.push(rx); self } /// Spawn the server under an explicit supervisor pid (via [`spawn_under`]) /// so it slots into the supervision tree. pub fn under(mut self, supervisor: Pid) -> Self { self.supervisor = Some(supervisor); self } /// Spawn the server actor and hand back its [`ServerRef`]. The server's /// lifetime is governed by its refs, not by joining, so the backing join /// handle is dropped. pub fn start(self) -> ServerRef { let (tx, rx) = channel::>(); let ServerBuilder { state, infos, supervisor } = self; let handle = match supervisor { Some(sup) => spawn_under(sup, move || server_loop::(rx, state, infos)), None => spawn(move || server_loop::(rx, state, infos)), }; ServerRef { tx, pid: handle.pid() } } } /// Spawn `state` as a server under the current actor (via [`spawn`]). Returns a /// [`ServerRef`]. Shorthand for `ServerBuilder::new(state).start()`. pub fn start(state: G) -> ServerRef { ServerBuilder::new(state).start() } /// Like [`start`], but spawns the server under an explicit supervisor pid (via /// [`spawn_under`]) so it slots into the supervision tree. pub fn start_under(supervisor: Pid, state: G) -> ServerRef { ServerBuilder::new(state).under(supervisor).start() } fn server_loop( rx: Receiver>, state: G, mut infos: Vec>, ) { // terminate() must run on every exit path (clean close, panic, stop), so it // lives in this guard's Drop rather than after the loop. struct Terminate(G); impl Drop for Terminate { fn drop(&mut self) { self.0.terminate(); } } fn dispatch(state: &mut G, env: Envelope) { match env { Envelope::Call(request, reply_tx) => { let reply = state.handle_call(request); // The caller may have gone away (e.g. cancelled while parked); // a failed reply send is not the server's problem. let _ = reply_tx.send(reply); } Envelope::Cast(request) => state.handle_cast(request), } } let mut guard = Terminate(state); // The control arm: Watchers feed Monitors to the loop through it. The // ctx (and with it the loop's own sender) drops right after init — a // state that didn't clone the Watcher closes the arm, the first select // observes the closure, and the loop falls back to the plain-inbox park. let (watch_tx, watch_rx) = channel::(); guard.0.init(&ServerCtx { watcher: Watcher { tx: watch_tx } }); let mut monitors: Vec = Vec::new(); let mut watch_open = true; loop { if monitors.is_empty() && !watch_open && infos.is_empty() { // Nothing to select over: park on the inbox alone, exactly the // pre-v0.8 loop. match rx.recv() { Ok(env) => dispatch(&mut guard.0, env), // All ServerRefs dropped → inbox closed → graceful shutdown. Err(_) => break, } } else { // Arm priority: downs, then the control arm, then infos (each in // declaration order), then the inbox. The arm slice is rebuilt // per iteration because every set but the inbox shrinks or grows. let nd = monitors.len(); let nw = watch_open as usize; let i = { let mut arms: Vec<&dyn Selectable> = Vec::with_capacity(nd + nw + infos.len() + 1); for m in &monitors { arms.push(&m.rx); } if watch_open { arms.push(&watch_rx); } for r in &infos { arms.push(r); } arms.push(&rx); select(&arms) }; if i < nd { // A Down retires its arm either way: delivered (one-shot) or // closed without delivering (defensive; shouldn't happen). let m = monitors.remove(i); if let Ok(Some(down)) = m.rx.try_recv() { guard.0.handle_down(down); } } else if i < nd + nw { match watch_rx.try_recv() { Ok(Some(m)) => monitors.push(m), // Single-receiver: nothing can drain the arm between // select's ready and our try_recv. Ok(None) => debug_assert!(false, "ready control arm was empty"), // Every Watcher gone: stop selecting on the arm. Err(_) => watch_open = false, } } else if i < nd + nw + infos.len() { let j = i - nd - nw; match infos[j].try_recv() { Ok(Some(info)) => guard.0.handle_info(info), Ok(None) => debug_assert!(false, "ready info arm was empty"), // Senders all gone: drop the arm, keep serving. `remove` // (not swap_remove) — order is priority. Err(_) => { infos.remove(j); } } } else { match rx.try_recv() { Ok(Some(env)) => dispatch(&mut guard.0, env), Ok(None) => debug_assert!(false, "ready inbox was empty"), Err(_) => break, } } } // Observation point so a server whose arms are never empty stays // preemptible and cancellable. crate::check!(); } }