//! 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`](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). //! //! ## Time (timers and idle) //! //! A server arms timers through a [`TimerHandle`] cloned from the [`ServerCtx`] //! in `init` and stored on the state — the same shape as [`Watcher`] for //! monitors. [`arm_after`](TimerHandle::arm_after) is a one-shot, //! [`tick_every`](TimerHandle::tick_every) a periodic; both fire into //! [`handle_timer`](GenServer::handle_timer) at *system priority* (above infos //! and the inbox, by arm position), and [`cancel`](TimerHandle::cancel) carries //! the substrate's race signal. Separately, [`ServerCtx::idle_after`] sets a //! receive-timeout window: quiet for the whole window fires //! [`handle_idle`](GenServer::handle_idle). //! //! Two unrelated things share the word *timeout*: the server-side **idle / //! receive timeout** above, and the client-side **call deadline** //! ([`ServerRef::call_timeout`]) — how long a caller waits for a reply. They sit //! on different axes and never interact (RFC 015 §7). //! //! ## 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.) //! The idle window is likewise set once, in `init` (RFC 015 §4.4). use crate::channel::{channel, select, select_timeout, Receiver, RecvTimeoutError, Selectable, Sender}; use crate::monitor::{demonitor, monitor, Down, Monitor}; use crate::pid::Pid; use crate::registry::{register_with, resolve_named_sender, RegisterError}; use crate::scheduler::{cancel_timer, request_stop, send_after_to, spawn, spawn_under}; use crate::timer::TimerId; use std::cell::Cell; use std::collections::HashMap; use std::marker::PhantomData; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; /// 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; /// The server's own scheduled-timer payload, delivered to /// [`handle_timer`](Self::handle_timer) when a timer armed via the loop's /// [`TimerHandle`] fires (RFC 015). Kept distinct from /// [`Info`](Self::Info) — `Info` is *external* out-of-band traffic, `Timer` /// is the server's *own* fires — so the system/userspace split stays /// legible and a tagged timer round-trips /// (`arm_after(d, Tk::Retry(n))` → `handle_timer(Tk::Retry(n))`). Use `()` /// if unused, exactly as `Info` does. type Timer: 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) where Self: Sized, { } /// 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) {} /// Handle a fired timer armed through the loop's [`TimerHandle`] /// ([`arm_after`](TimerHandle::arm_after) / /// [`tick_every`](TimerHandle::tick_every)). Timer fires re-enter at system /// priority — above infos and the inbox — so a heartbeat cannot be starved /// by userspace traffic. Default: drop it (RFC 015 §6). fn handle_timer(&mut self, _msg: Self::Timer) {} /// Handle a receive/idle timeout: fired when the loop has waited a full /// idle window (set once via [`ServerCtx::idle_after`]) with no message of /// any kind dispatched. The window resets on every dispatched message and /// re-arms after this fires (a steady idle detector); a server wanting /// one-shot idle-shutdown simply requests its own stop here. Default: no-op /// (RFC 015 §4.4, §6). fn handle_idle(&mut self) {} /// 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`]. /// /// This `timeout` is a **client-side call deadline** — how long *this caller* /// waits for a reply — and is wholly separate from the server-side idle / /// receive timeout ([`ServerCtx::idle_after`] → /// [`GenServer::handle_idle`]), which measures quiet on the *server's* inbox. /// Same word ("timeout"), two different axes (RFC 015 §7); neither touches /// the other. /// /// 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) } /// Ask the server to terminate and block until it has — the `sys`-style /// stop (cf. Erlang's `gen_server:stop/1`). Sends a cooperative stop to the /// server actor and waits for its `Down`, so [`GenServer::terminate`] has /// run by the time this returns (it fires from the loop's drop guard on the /// stop unwind). Returns immediately if the server was already gone. /// /// This is the explicit teardown for a server pinned alive by a registered /// [`ServerName`] (whose stored sender means dropping every external /// [`ServerRef`] no longer closes the inbox). Best-effort like all /// cooperative cancellation: a server wedged in a tight loop with no /// observation point cannot be stopped. Panics if called outside /// `Runtime::run()`. pub fn shutdown(&self) { let mon = monitor(self.pid); request_stop(self.pid); // The Down lands when the server finalizes; an already-dead target makes // `monitor` deliver NoProc immediately, so this never blocks forever. let _ = mon.rx.recv(); demonitor(&mon); } } /// The server loop's internal *system intake* channel (RFC 015 §4.5). Control /// (monitor handoff) and armed-timer fires are structurally the same — both are /// loop-internal sources selected above the inbox — so they fold into one /// channel, dispatched by variant. The arm stays open while any [`Watcher`] or /// [`TimerHandle`] clone (or an in-flight fire thunk) still holds a sender. enum Sys { /// A monitor handed in via [`Watcher::watch`]; pushed onto the loop's /// monitor set. Watch(Monitor), /// A fired one-shot timer carrying its loop-local id (so the loop can retire /// it from the live set) and the server's payload, dispatched to /// [`GenServer::handle_timer`]. Timer(crate::timer::TimerId, G::Timer), /// A fired periodic tick carrying its stable local id. The loop resolves the /// payload from the registry's factory, dispatches it to /// [`GenServer::handle_timer`], and re-arms the next tick (RFC 015 §4.3). Tick(crate::timer::TimerId), } /// The server loop's runtime hook, passed to [`GenServer::init`]. Hands out the /// loop's two clonable intake handles — the [`Watcher`] (monitors) and the /// [`TimerHandle`] (timers) — plus the one-shot idle-window setter. Opaque, so /// fields can grow without breaking. pub struct ServerCtx { sys_tx: Sender>, reg: Arc>>, /// The idle/receive-timeout window, set once via [`idle_after`](Self::idle_after) /// during `init` and read by the loop after `init` returns. Interior-mutable /// because `init` holds only `&ctx`; not `Send`, but `ServerCtx` is only ever /// borrowed on the actor's own stack during `init`, never sent. idle: Cell>, } 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 { Watcher { tx: self.sys_tx.clone() } } /// Shorthand for `ctx.watcher().watch(m)` when watching during `init`. pub fn watch(&self, m: Monitor) { self.watcher().watch(m) } /// A clonable handle to the loop's timer intake (the [`Watcher`] pattern, /// for time). Store it on the state during `init` to /// [`arm_after`](TimerHandle::arm_after) / /// [`tick_every`](TimerHandle::tick_every) / /// [`cancel`](TimerHandle::cancel) from any later handler. pub fn timer(&self) -> TimerHandle { TimerHandle { sys_tx: self.sys_tx.clone(), reg: self.reg.clone() } } /// Set the idle / receive-timeout window: if no message of any kind is /// dispatched for `after`, the loop fires [`GenServer::handle_idle`] /// (RFC 015 §4.4). Set once, in `init`; the window is loop-owned and fixed /// (dynamic per-message reconfiguration is a non-goal). The window resets on /// every dispatched message and re-arms after `handle_idle` (a steady idle /// detector); a server wanting one-shot idle-shutdown requests its own stop /// in `handle_idle`. /// /// Distinct from [`ServerRef::call_timeout`], which is a *client-side* call /// deadline — same word, different axis (§7). pub fn idle_after(&self, after: Duration) { self.idle.set(Some(after)); } } /// Per-server timer bookkeeping, shared between the loop and every /// [`TimerHandle`] clone. A gen_server actor is single-threaded — handle calls /// (made from inside handlers) and the loop never run concurrently — so this /// `Mutex` is always uncontended; it is `Arc` rather than `Rc` /// only because the handle is stored on `self` and `GenServer: Send` forces the /// handle (hence its shared state) to be `Send`. struct TimerReg { /// Monotonic minter for loop-local [`TimerId`]s — the ids handed to users, /// kept distinct from the substrate `seq` (which changes on every periodic /// re-arm). A local id is resolved only through this registry, never passed /// to [`cancel_timer`], so the two id roles never mix. next_local: u64, /// Live one-shot timers: local id → current substrate id. Present from /// [`arm_after`](TimerHandle::arm_after) until the loop retires it on fire /// or [`cancel`](TimerHandle::cancel) removes it. oneshots: HashMap, /// Live periodic timers: stable local id → its bookkeeping. The stable id /// is minted once by [`tick_every`](TimerHandle::tick_every) and survives /// every re-arm; the entry lives until [`cancel`](TimerHandle::cancel) /// removes it (steady re-arm never self-removes). periodics: HashMap>, /// A `Sys` sender held *only while ≥1 periodic is live*, used by the loop to /// re-arm the next tick. `Some` exactly when `periodics` is non-empty: this /// keeps the system arm open for a server whose only system use is a /// periodic, while leaving a non-timer server's arm to auto-close (the /// `unused_ctx` behaviour) since this stays `None` for it. rearm_tx: Option>>, } /// One periodic timer's loop-side bookkeeping. struct Periodic { /// Re-arm interval; each fire schedules the next at `now + every` (§4.3). every: Duration, /// The currently-armed substrate timer for this periodic (changes on every /// re-arm); cancelled by [`cancel`](TimerHandle::cancel). live: crate::timer::TimerId, /// Produces a fresh payload for each tick. Built in `tick_every` as /// `move || msg.clone()` — so the `Clone` bound lives there, on the one /// method that needs it, and never leaks onto `type Timer` or the loop /// (which is monomorphised per server and only *calls* this box). make: Box G::Timer + Send>, } // Manual Default: deriving would demand `G: Default`, but a fresh registry is // independent of the server type. impl Default for TimerReg { fn default() -> Self { TimerReg { next_local: 0, oneshots: HashMap::new(), periodics: HashMap::new(), rearm_tx: None, } } } impl TimerReg { /// Mint the next loop-local id. fn mint(&mut self) -> TimerId { let id = TimerId::from_raw(self.next_local); self.next_local = self.next_local.wrapping_add(1); id } } /// Arms, cancels, and (chunk 4) periodically ticks server timers, the time-side /// twin of [`Watcher`] (RFC 015 §4.3). Handed out by [`ServerCtx::timer`] in /// `init` and stored on the state; later handlers arm timers without any change /// to their signatures. Clonable; the arm stays open while any clone (or an /// in-flight fire) lives. pub struct TimerHandle { sys_tx: Sender>, reg: Arc>>, } // Manual Clone for the same reason as `Watcher`: no `G: Clone` needed. impl Clone for TimerHandle { fn clone(&self) -> Self { TimerHandle { sys_tx: self.sys_tx.clone(), reg: self.reg.clone() } } } impl TimerHandle { /// Arm a one-shot timer: deliver `msg` to [`GenServer::handle_timer`] after /// `after`, unless [`cancel`](Self::cancel)led first. Returns a loop-local /// [`TimerId`] for cancellation. A thin wrapper over the `send_after` /// substrate that lands the fire on the loop's own system arm (above infos /// and the inbox), not in the inbox. pub fn arm_after(&self, after: Duration, msg: G::Timer) -> TimerId { let mut reg = self.reg.lock().unwrap(); let local = reg.mint(); // The fire thunk carries the local id so the loop can retire the entry // on dispatch; `msg` is moved in (no `Clone` needed for one-shots). let sub = send_after_to(after, self.sys_tx.clone(), Sys::Timer(local, msg)); reg.oneshots.insert(local, sub); local } /// Arm a periodic timer: deliver a fresh `msg` to /// [`GenServer::handle_timer`] every `every`, until /// [`cancel`](Self::cancel)led. Loop-managed sugar over the one-shot /// substrate (RFC 015 §4.3): the substrate stays one-shot; the loop re-arms /// at `now + every` after each fire and exposes one stable [`TimerId`], so a /// single `cancel` stops the re-arm *and* the pending instance. /// /// Requires `Self::Timer: Clone` — a periodic re-delivers the same logical /// message each tick, so the loop needs a fresh copy per period. The bound /// sits on this method alone; one-shot [`arm_after`](Self::arm_after) and /// the `type Timer` declaration stay unconstrained. pub fn tick_every(&self, every: Duration, msg: G::Timer) -> TimerId where G::Timer: Clone, { let mut reg = self.reg.lock().unwrap(); let local = reg.mint(); // A live periodic must keep the system arm open so the next tick can be // re-armed; the first periodic installs the loop's re-arm sender. if reg.periodics.is_empty() { reg.rearm_tx = Some(self.sys_tx.clone()); } let make: Box G::Timer + Send> = Box::new(move || msg.clone()); // First instance fires after `every`; the payload is produced loop-side // from `make` on fire, so the tick carries only the stable id. let sub = send_after_to(every, self.sys_tx.clone(), Sys::Tick(local)); reg.periodics.insert(local, Periodic { every, live: sub, make }); local } /// Cancel an armed timer (one-shot or periodic). Returns the substrate's /// race signal — `true` if the cancel beat the (pending) fire, `false` if /// it had already fired / been cancelled / is unknown. For a periodic this /// also stops the re-arm: the entry is dropped, so the loop will not /// schedule another tick even if the pending one already escaped onto the /// channel (that in-flight tick is discarded on dispatch). pub fn cancel(&self, id: TimerId) -> bool { let mut reg = self.reg.lock().unwrap(); if let Some(sub) = reg.oneshots.remove(&id) { return cancel_timer(sub); } if let Some(p) = reg.periodics.remove(&id) { let beat = cancel_timer(p.live); if reg.periodics.is_empty() { reg.rearm_tx = None; } return beat; } false } } /// 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. pub struct Watcher { tx: Sender>, } // Manual Clone: deriving would demand `G: Clone`, but the handle is clonable // regardless of the server type (it clones only the inner sender). impl Clone for Watcher { fn clone(&self) -> Self { Watcher { tx: self.tx.clone() } } } 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(Sys::Watch(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 { self.spawn_server() } /// Bind the server to a durable [`ServerName`] as it starts. Switches to the /// fallible [`NamedServerBuilder::start`] (the name may already be held by a /// live server). Consumes the builder, carrying its `with_info` / `under` /// configuration through. pub fn named(self, name: ServerName) -> NamedServerBuilder { NamedServerBuilder { builder: self, name: name.as_str() } } /// The shared spawn body behind [`start`](Self::start) and /// [`NamedServerBuilder::start`]: make the inbox, spawn the loop, return the /// ref. (The named path additionally publishes the inbox sender under the /// name before returning.) fn spawn_server(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() } } } /// A durable name typed by the *server* (RFC 014): a gen_server is multi-message /// (call / cast over one inbox), so it is addressed by `G` rather than by a /// single message type. By-name [`call`] / [`cast`] check the request against /// `G`'s `Call` / `Cast` / `Reply`. Declared as a constant and shared freely: /// /// ```ignore /// const COUNTER: ServerName = ServerName::new("counter"); /// ``` /// /// Under the hood the server's inbox is published into the registry as a /// `Sender>` keyed by its message `TypeId` — the same channel store /// every other name uses — so naming needs no separate directory. `Envelope` /// stays private: only `ServerName` opens that door. pub struct ServerName { name: &'static str, _marker: PhantomData G>, } impl ServerName { /// Bind a static string as a server name. `const`, so names live as /// associated constants at call sites. #[inline] pub const fn new(name: &'static str) -> Self { Self { name, _marker: PhantomData } } /// The underlying registry key. #[inline] pub const fn as_str(self) -> &'static str { self.name } } impl Copy for ServerName {} impl Clone for ServerName { fn clone(&self) -> Self { *self } } /// A [`ServerBuilder`] that will bind a [`ServerName`] as it starts. Reached via /// [`ServerBuilder::named`]; its [`start`](Self::start) is fallible because the /// name may already be held by a live server. `with_info` / `under` stay /// available so configuration can come before or after `named`. pub struct NamedServerBuilder { builder: ServerBuilder, name: &'static str, } impl NamedServerBuilder { /// Add an out-of-band info channel (see [`ServerBuilder::with_info`]). pub fn with_info(mut self, rx: Receiver) -> Self { self.builder = self.builder.with_info(rx); self } /// Spawn under an explicit supervisor (see [`ServerBuilder::under`]). pub fn under(mut self, supervisor: Pid) -> Self { self.builder = self.builder.under(supervisor); self } /// Spawn the server and bind its name in one step. Fallible: returns /// [`RegisterError::NameTaken`] if the name is already held by a different /// live server. /// /// The inbox sender is published under the name **from the parent side, /// before this returns**, so a by-name `call` / `cast` resolves the instant /// `start()` returns — no race with the server body. On a name clash the /// just-spawned server is wound down (its only ref is dropped, closing the /// inbox), so a failed bind leaks no actor. pub fn start(self) -> Result, RegisterError> { let NamedServerBuilder { builder, name } = self; let server = builder.spawn_server(); match register_with::>(server.pid, name, server.tx.clone()) { Ok(()) => Ok(server), Err(e) => { drop(server); // inbox closes → loop exits gracefully Err(e) } } } } /// Resolve a [`ServerName`] to a [`ServerRef`] when you want a handle to hold or /// pass on rather than resolve per call. Rebuilds the ref from the registry's /// stored inbox sender; `None` if no live server holds the name. /// /// Panics if called outside `Runtime::run()`. pub fn whereis_server(name: ServerName) -> Option> { resolve_named_sender::>(name.as_str()).map(|(pid, tx)| ServerRef { tx, pid }) } /// Synchronous request-reply to the server currently registered under `name`, /// resolving through the registry on every call (so a server restarted under /// the same name is reached transparently). [`CallError::ServerDown`] if no live /// server holds the name, or if it dies before replying. /// /// Panics if called outside `Runtime::run()`. pub fn call(name: ServerName, request: G::Call) -> Result { match whereis_server(name) { Some(server) => server.call(request), None => Err(CallError::ServerDown), } } /// Fire-and-forget to the server registered under `name`, resolving per cast. /// [`CastError::ServerDown`] if no live server holds the name. /// /// Panics if called outside `Runtime::run()`. pub fn cast(name: ServerName, request: G::Cast) -> Result<(), CastError> { match whereis_server(name) { Some(server) => server.cast(request), None => Err(CastError::ServerDown), } } /// Terminate the server registered under `name` and block until it is down (see /// [`ServerRef::shutdown`]). A no-op if no live server holds the name. /// /// Panics if called outside `Runtime::run()`. pub fn shutdown(name: ServerName) { if let Some(server) = whereis_server(name) { server.shutdown(); } } /// 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. The guard also owns // a registry handle so the *same* drop drains every live timer (RFC 015 // §4.7): once the loop is gone no periodic can re-arm anyway, but cancelling // here keeps no armed substrate entry lingering past the server, and the // assert pins the invariant. struct Terminate(G, Arc>>); impl Drop for Terminate { fn drop(&mut self) { { let mut reg = self.1.lock().unwrap(); for (_, sub) in reg.oneshots.drain() { cancel_timer(sub); } for (_, p) in reg.periodics.drain() { cancel_timer(p.live); } reg.rearm_tx = None; // A fired-but-gone send already no-ops on the closed Sys channel; // this guards the armed-but-unfired re-arming case specifically. debug_assert!( reg.oneshots.is_empty() && reg.periodics.is_empty(), "an armed timer survived gen_server loop exit" ); } 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), } } // Shared timer bookkeeping: the loop keeps one clone (to retire one-shots on // fire, re-arm periodics, and read the idle window); the guard holds another // to drain on exit; every TimerHandle the state stores holds more. let reg = Arc::new(Mutex::new(TimerReg::default())); let mut guard = Terminate(state, reg.clone()); // The system intake arm: Watchers feed Monitors and armed timers feed fires // to the loop through it. The ctx (and with it the loop's own sender) drops // right after init — a state that cloned no Watcher/TimerHandle closes the // arm, the first select observes the closure, and the loop falls back to the // plain-inbox park. let (sys_tx, sys_rx) = channel::>(); // Bind the ctx so the idle window set during init can be read back, then // drop it — that drops the loop's own Sys sender, so a state that cloned no // Watcher/TimerHandle lets the arm auto-close (the unused-ctx behaviour). let ctx = ServerCtx { sys_tx, reg: reg.clone(), idle: Cell::new(None) }; guard.0.init(&ctx); let idle = ctx.idle.get(); drop(ctx); let mut monitors: Vec = Vec::new(); let mut sys_open = true; // The receive-timeout deadline, live only when an idle window is set. Reset // to `now + idle` after any dispatched message and after handle_idle fires // (steady detector); `None` disables the timeout entirely. let mut idle_deadline: Option = idle.map(|d| Instant::now() + d); let reset_idle = |dl: &mut Option| { if let Some(d) = idle { *dl = Some(Instant::now() + d); } }; loop { if monitors.is_empty() && !sys_open && infos.is_empty() { // Nothing to select over: park on the inbox alone (the pre-v0.8 // loop), with the idle window as the recv timeout when one is set. match idle_deadline { Some(dl) => { let wait = dl.saturating_duration_since(Instant::now()); match rx.recv_timeout(wait) { Ok(env) => { dispatch(&mut guard.0, env); reset_idle(&mut idle_deadline); } // Quiet for the whole window → the idle event. Err(RecvTimeoutError::Timeout) => { guard.0.handle_idle(); reset_idle(&mut idle_deadline); } // All ServerRefs dropped → inbox closed → shutdown. Err(RecvTimeoutError::Disconnected) => break, } } None => match rx.recv() { Ok(env) => dispatch(&mut guard.0, env), Err(_) => break, }, } } else { // Arm priority: downs, then the system 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. // The whole wait carries the idle window as its timeout. let nd = monitors.len(); let nw = sys_open as usize; let sel = { let mut arms: Vec<&dyn Selectable> = Vec::with_capacity(nd + nw + infos.len() + 1); for m in &monitors { arms.push(&m.rx); } if sys_open { arms.push(&sys_rx); } for r in &infos { arms.push(r); } arms.push(&rx); match idle_deadline { Some(dl) => { select_timeout(&arms, dl.saturating_duration_since(Instant::now())) } None => Some(select(&arms)), } }; let i = match sel { Some(i) => i, // No arm ready for the whole window → idle, then re-arm steady. None => { guard.0.handle_idle(); reset_idle(&mut idle_deadline); crate::check!(); continue; } }; 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); reset_idle(&mut idle_deadline); } } else if i < nd + nw { match sys_rx.try_recv() { // Control intake, not a dispatched message: no idle reset. Ok(Some(Sys::Watch(m))) => monitors.push(m), Ok(Some(Sys::Timer(id, msg))) => { // The one-shot fired: retire its registry entry so the // live set tracks only still-pending timers, then // dispatch. reg.lock().unwrap().oneshots.remove(&id); guard.0.handle_timer(msg); reset_idle(&mut idle_deadline); } Ok(Some(Sys::Tick(id))) => { // A periodic fired. Under the lock: if it is still live // (cancel didn't beat us here), produce its payload and // re-arm the next tick at now + every *before* dispatch, // so the period is measured from fire-handling and a // handler that cancels stops the just-armed instance. let msg = { let mut g = reg.lock().unwrap(); let r = &mut *g; if let Some(p) = r.periodics.get_mut(&id) { let every = p.every; let msg = (p.make)(); let tx = r .rearm_tx .as_ref() .expect("a live periodic keeps rearm_tx Some") .clone(); p.live = send_after_to(every, tx, Sys::Tick(id)); Some(msg) } else { // Cancelled between fire and dispatch: discard. None } }; if let Some(msg) = msg { guard.0.handle_timer(msg); reset_idle(&mut idle_deadline); } } // Single-receiver: nothing can drain the arm between // select's ready and our try_recv. Ok(None) => debug_assert!(false, "ready system arm was empty"), // Every Watcher/TimerHandle gone: stop selecting on the arm. Err(_) => sys_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); reset_idle(&mut idle_deadline); } 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); reset_idle(&mut idle_deadline); } 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!(); } }