//! Named mailbox registry — resolve a name (or pid) to a *messageable* actor. //! //! ## What changed (RFC 014) //! //! The old registry was a `name <-> pid` bimap: `whereis` handed back a `Pid` //! you could not send to, because a pid is just `(index, generation)` with no //! delivery endpoint. This rework makes resolution yield something messageable. //! //! Two facts shape the structure: //! //! 1. **A name resolves to a single actor.** Many actors under one label is //! what *process groups* (`pg`) are for; the registry is one-name-one-actor //! (several names *may* point at the same actor). //! 2. **Channels are typed**, so an actor has no single untyped mailbox. An //! actor instead owns a *set* of typed channels — one [`Sender`] per message //! type it accepts. So the registry maps name/pid to a [`Mailbox`]: a small //! structure holding that actor's pid plus all of its typed channels, keyed //! by message [`TypeId`]. //! //! Resolution is therefore: `name -> pid` (single actor) `-> Mailbox -> the //! channel for message type M`. A `Name` and a `Name` on the *same* //! actor select *different* channels purely by their type parameter, so //! capability separation (RFC 014 §4.7) needs no extra machinery. //! //! ## Type erasure is contained //! //! Each stored channel is a `Box` that is concretely a //! `Sender`, filed under `TypeId::of::()`. A resolve for `M` looks up //! that exact `TypeId` and downcasts to `Sender` — keyed by the very type we //! downcast to, so the downcast cannot fail on correct data; a failure is a //! smarm bug, asserted in debug. The phantom `M` on [`Name`] re-imposes the //! type at the call site, so callers never touch the erasure. //! //! ## Cleanup is lazy (prune-on-contact) //! //! As before, there is no `finalize` hook and no name field on the slot. Every //! operation that touches a binding checks the target pid's liveness via the //! generation-checked slot word; a binding to a dead actor behaves as absent //! and is pruned on contact (its [`Mailbox`] and every name pointing at it are //! dropped). The cost is a dead binding lingering until something looks at it; //! the payoff is zero coupling to the actor lifecycle. //! //! ## Locking //! //! One `RawMutex` (Leaf class) in `RuntimeInner`, exactly like the old //! registry. The fold (name index *and* handles under the one lock) is what //! keeps a name-addressed `send` on a single Leaf — `raw_mutex` panics on a //! second Leaf acquired while one is held. The send path clones the `Sender` //! **under** the Leaf lock (a `Sender::clone` takes a Channel lock, permitted //! under a Leaf), then **releases** the Leaf and only *then* sends — a send can //! unpark a receiver, and wakeup-bearing work runs outside the Leaf. Order is //! **Leaf -> Channel**, as `pg`/`finalize`. use crate::channel::Sender; use crate::pid::{Addressable, Name, Pid}; use crate::scheduler::{self_pid, with_runtime}; use std::any::{type_name, Any, TypeId}; use std::collections::HashMap; /// Why a [`register`] call was rejected. #[derive(Debug, Clone, PartialEq, Eq)] pub enum RegisterError { /// The name is bound to a different, still-live actor. NameTaken { holder: Pid }, /// The caller is not a live actor (cannot happen for `self`, kept for /// symmetry / future explicit-pid registration). NoProc, } impl std::fmt::Display for RegisterError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RegisterError::NameTaken { holder } => { write!(f, "name is already registered to live actor {holder}") } RegisterError::NoProc => write!(f, "caller is not a live actor"), } } } impl std::error::Error for RegisterError {} /// Why a name-addressed [`send`] did not deliver. Carries the message back so /// the caller never loses it (mirrors [`crate::channel::SendError`]). /// /// `Debug`/`Display` are hand-written so neither demands `M: Debug` — the /// payload is returned, not printed. pub enum SendError { /// No live actor is currently registered under this name. Name-addressed /// [`send`] only; the pid-addressed counterpart is [`SendError::Dead`]. Unresolved(M), /// The pid-addressed actor is no longer the live incarnation this pid names /// — it has died, even if its slot now holds a *different* actor (a direct /// `Pid` send never redirects; contrast name-addressed [`send`]). Pid /// paths ([`send_to`] / [`send_dyn`]) only. Dead(M), /// The actor is live but exposes no channel for this message type. NoChannel(M), /// The actor's channel for this message type is closed (its receiver is gone). Closed(M), /// No live member to deliver to — a [`dispatch`](crate::dispatch) over an /// empty (or all-dead) process group. Group-addressed dispatch only; the /// name-addressed counterpart is [`SendError::Unresolved`]. The message is /// handed back undelivered. NoMember(M), } impl SendError { /// Recover the undelivered message. pub fn into_inner(self) -> M { match self { SendError::Unresolved(m) | SendError::Dead(m) | SendError::NoChannel(m) | SendError::Closed(m) | SendError::NoMember(m) => m, } } fn variant(&self) -> &'static str { match self { SendError::Unresolved(_) => "Unresolved", SendError::Dead(_) => "Dead", SendError::NoChannel(_) => "NoChannel", SendError::Closed(_) => "Closed", SendError::NoMember(_) => "NoMember", } } } impl std::fmt::Debug for SendError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "SendError::{}", self.variant()) } } impl std::fmt::Display for SendError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { SendError::Unresolved(_) => write!(f, "no live actor registered under that name"), SendError::Dead(_) => write!(f, "the addressed actor is no longer the live incarnation"), SendError::NoChannel(_) => write!(f, "actor has no channel for this message type"), SendError::Closed(_) => write!(f, "the actor's channel for this type is closed"), SendError::NoMember(_) => write!(f, "no live member in the process group"), } } } impl std::error::Error for SendError {} /// A registry-stored channel, type-erased over its message type. The stored /// object must serve two readers: `clone_sender` (downcast back to the concrete /// `Sender`) and the RFC 016 snapshot (queued length without knowing `M`). /// A bare `Box` gives the first but not the second, so we erase behind /// this small trait instead. trait ErasedSender: Send { fn as_any(&self) -> &dyn Any; fn queued_len(&self) -> usize; } impl ErasedSender for Sender { fn as_any(&self) -> &dyn Any { self } fn queued_len(&self) -> usize { Sender::queued_len(self) } } /// One typed channel of an actor, type-erased. Concretely a `Sender` filed /// under `TypeId::of::()`; `msg_type` is `type_name::()`, kept for /// observers (RFC 014 §4.5) and as the debug cross-check on the downcast. struct Channel { sender: Box, msg_type: &'static str, } /// An actor's messageable surface: its identity plus every typed channel it has /// published, keyed by message [`TypeId`]. Stored once per live actor; reached /// by pid (directly) or by any name pointing at that pid. struct Mailbox { pid: Pid, channels: HashMap, } impl Mailbox { fn new(pid: Pid) -> Self { Self { pid, channels: HashMap::new() } } /// Clone the `Sender` for this actor, if it has one. Called **under the /// registry Leaf lock**: `Sender::clone` takes a Channel lock, which is /// legal under a Leaf (Leaf -> Channel). fn clone_sender(&self) -> Option> { let ch = self.channels.get(&TypeId::of::())?; let tx = match ch.sender.as_any().downcast_ref::>() { Some(tx) => tx, None => panic!( "smarm: channel keyed by TypeId but downcast to its own type failed (core corrupt)" ), }; debug_assert_eq!(ch.msg_type, type_name::(), "msg_type / TypeId disagree"); Some(tx.clone()) } } /// Per-actor registry view handed to RFC 016 introspection: registered names /// and summed mailbox depth, tagged with the mailbox's `pid` so a stale /// incarnation can be filtered against the slab. Covers only *published* /// channels (`register` / `install` / `spawn_addr` / gen_server start); an /// actor that holds only a private `channel()` receiver is invisible here and /// reports depth 0. pub(crate) struct MailboxInfo { pub(crate) pid: Pid, pub(crate) names: Vec<&'static str>, pub(crate) depth: u32, } /// The directory. Invariant (held under the registry lock): every value in /// `by_name` is the index of a [`Mailbox`] present in `by_index`, and that /// mailbox's `pid.index()` equals the key. Stale entries (dead actors) violate /// nothing — they are simply pruned on contact. pub(crate) struct Registry { /// `pid.index() -> the actor's mailbox`. The handle store. by_index: HashMap, /// `name -> pid.index()`. Several names may map to one actor. by_name: HashMap<&'static str, u32>, } impl Registry { pub(crate) fn new() -> Self { Self { by_index: HashMap::new(), by_name: HashMap::new() } } /// Drop a dead actor's mailbox and every name that pointed at it. fn prune(&mut self, index: u32) { self.by_index.remove(&index); self.by_name.retain(|_, idx| *idx != index); } /// RFC 016 snapshot input: per-slot-index registry view — the actor's /// registered names (inverted from `by_name`) and its mailbox depth (queued /// messages summed across every published typed channel). Built in one pass /// under the registry Leaf; the per-channel `queued_len` takes a Channel /// lock, legal under the Leaf (Leaf → Channel). Carries each mailbox's full /// `pid` so the caller can discard a stale incarnation's entry against the /// slab's live generation. Names that dangle (point at no mailbox) are /// dropped — they violate no invariant and get pruned on next contact. pub(crate) fn introspect_map(&self) -> HashMap { let mut names: HashMap> = HashMap::new(); for (&name, &idx) in &self.by_name { names.entry(idx).or_default().push(name); } let mut out: HashMap = HashMap::with_capacity(self.by_index.len()); for (&idx, mb) in &self.by_index { let depth: usize = mb.channels.values().map(|c| c.sender.queued_len()).sum(); out.insert( idx, MailboxInfo { pid: mb.pid, names: names.remove(&idx).unwrap_or_default(), depth: depth.min(u32::MAX as usize) as u32, }, ); } out } /// Single-actor form of [`introspect_map`](Self::introspect_map): the /// registry view for one slot index, or `None` if no mailbox is published /// there. Used by `actor_info` so its cost stays proportional to the one /// actor rather than locking every channel in the runtime. pub(crate) fn introspect_one(&self, idx: u32) -> Option { let mb = self.by_index.get(&idx)?; let depth: usize = mb.channels.values().map(|c| c.sender.queued_len()).sum(); let names = self .by_name .iter() .filter_map(|(&n, &i)| (i == idx).then_some(n)) .collect(); Some(MailboxInfo { pid: mb.pid, names, depth: depth.min(u32::MAX as usize) as u32 }) } } /// Is `pid` a live actor right now? Atomic slot-word read; no lock. fn live(inner: &crate::runtime::RuntimeInner, pid: Pid) -> bool { inner.slot_at(pid).is_some_and(|s| s.is_live_for(pid)) } /// Publish the current actor's `Sender` under `name`, capturing the channel /// so the name becomes messageable. Idempotent for the same `(name, type)`; /// registering a *second* type under the same (or another) name on the same /// actor just adds another channel to the actor's mailbox. /// /// Fails with [`RegisterError::NameTaken`] if the name is held by a *different* /// live actor (a binding to a dead actor is pruned and the name treated as /// free). Panics if called outside `Runtime::run()`. pub fn register(name: Name, tx: Sender) -> Result<(), RegisterError> { register_with(self_pid(), name.as_str(), tx) } /// Bind `name` to `pid`'s mailbox and publish `tx` under `M`'s [`TypeId`], for /// an explicit (already-live) actor rather than `self`. The shared core of /// [`register`] (which passes `self_pid()`) and the parent-side server-name /// bind in `gen_server` (which names a freshly spawned server before its body /// has run, so the name resolves the instant `start()` returns). Same collision /// rules and lock discipline as `register`. pub(crate) fn register_with( me: Pid, key: &'static str, tx: Sender, ) -> Result<(), RegisterError> { with_runtime(|inner| { let mut reg = inner.registry.lock(); if !live(inner, me) { return Err(RegisterError::NoProc); } if let Some(&holder_idx) = reg.by_name.get(key) { match reg.by_index.get(&holder_idx).map(|m| m.pid) { Some(holder) if holder == me => {} // same actor: just add the channel below Some(holder) if live(inner, holder) => { return Err(RegisterError::NameTaken { holder }); } Some(_) => reg.prune(holder_idx), // dead holder: free the name None => { reg.by_name.remove(key); // dangling name: free it } } } // Publish (or extend) the mailbox with this channel, then bind the name. publish_channel::(&mut reg, me, tx); reg.by_name.insert(key, me.index()); Ok(()) }) } /// Insert or extend the current actor's mailbox with one typed channel, filed /// under its message [`TypeId`]. Shared by [`register`] (which then binds a /// name) and [`install`] (which does not). A leftover mailbox at this slot /// index from a dead prior incarnation (pid mismatch) is replaced wholesale. /// Caller holds the registry lock and has established that `me` is live. fn publish_channel(reg: &mut Registry, me: Pid, tx: Sender) { let mb = reg.by_index.entry(me.index()).or_insert_with(|| Mailbox::new(me)); if mb.pid != me { *mb = Mailbox::new(me); } mb.channels.insert( TypeId::of::(), Channel { sender: Box::new(tx), msg_type: type_name::() }, ); } /// Publish the current actor's `Sender` into its mailbox **without** /// binding a name, and hand back the typed [`Pid`] that addresses this /// actor directly. This is the opt-in, lazy install of RFC 014 §5: an actor /// that wants to be reachable by a direct, identity-bound [`Pid`] (rather /// than only via a re-resolving [`Name`]) calls this once with its inbox /// sender, then hands the returned pid out. /// /// Unlike [`register`] there is no name to collide on, and `self` is always a /// live actor inside `run()`, so this is infallible. Panics if called outside /// `Runtime::run()`. pub fn install(tx: Sender) -> Pid { let me = self_pid(); with_runtime(|inner| { let mut reg = inner.registry.lock(); debug_assert!(live(inner, me), "self_pid() is a live actor inside run()"); publish_channel::(&mut reg, me, tx); }); // `me` is this actor; re-type the identity as `Pid` (the channel for // `A::Msg` was just published, so the typed address is now messageable). Pid::from_raw(me.raw()) } /// Publish `tx` into `pid`'s mailbox under `M`'s [`TypeId`], for an explicit /// (freshly minted, already-live) actor rather than `self`. The parent-side /// half of [`spawn_addr`](crate::spawn_addr): the spawner makes the inbox and /// publishes the sender here *before* handing back the `Pid`, so an immediate /// `send_to` on the returned pid always resolves — the address is live the /// instant the caller holds it, with no dependence on the body having run yet. /// /// Caller guarantees `pid` is the just-installed actor (Queued, this exact /// incarnation); `publish_channel` replaces any stale leftover at the slot. pub(crate) fn install_for(pid: Pid, tx: Sender) { with_runtime(|inner| { let mut reg = inner.registry.lock(); debug_assert!(live(inner, pid), "install_for: pid must be a freshly spawned, live actor"); publish_channel::(&mut reg, pid, tx); }); } /// The single actor currently registered under `name`, or `None` if unbound or /// no longer live (the stale binding is pruned on the way out). pub fn whereis(name: &str) -> Option { with_runtime(|inner| { let mut reg = inner.registry.lock(); let idx = *reg.by_name.get(name)?; match reg.by_index.get(&idx).map(|m| m.pid) { Some(pid) if live(inner, pid) => Some(pid), Some(_) => { reg.prune(idx); None } None => { reg.by_name.remove(name); None } } }) } /// Resolve `name` to a *typed* [`Pid`] — the identity-bound counterpart of /// [`whereis`] (RFC 014 §4.4). Recovers the compile-checked /// [`send_to`] path from a durable name: looks the name up, /// then re-types the erased pid as `Pid` via the unchecked /// [`assert_type`](crate::pid::assert_type) primitive. A wrong `A` is not /// unsound — it degrades to [`SendError::NoChannel`] on the next send (routing /// is by message `TypeId`), never a misdelivery. `None` if unbound or dead. /// /// Panics if called outside `Runtime::run()`. pub fn lookup_as(name: &str) -> Option> { whereis(name).map(crate::pid::assert_type::) } /// Resolve `name` to its actor's pid and a cloned `Sender`, under the Leaf /// lock (clone-under-lock, then release). The crate-internal building block for /// `gen_server`'s by-name addressing: a named server publishes its inbox as a /// `Sender>` (via [`register_with`]), and `whereis_server` / `call` /// / `cast` recover that exact typed sender here to rebuild a `GenServerRef`. /// `None` if unbound, dead (pruned on the way out), or holding no `M` channel. pub(crate) fn resolve_named_sender(name: &str) -> Option<(Pid, Sender)> { with_runtime(|inner| { let mut reg = inner.registry.lock(); let idx = *reg.by_name.get(name)?; let pid = match reg.by_index.get(&idx).map(|m| m.pid) { Some(pid) if live(inner, pid) => pid, Some(_) => { reg.prune(idx); return None; } None => { reg.by_name.remove(name); return None; } }; let tx = reg.by_index.get(&idx).and_then(Mailbox::clone_sender::)?; Some((pid, tx)) }) } /// Remove the binding for `name`, returning the actor it pointed at if still /// live. Only the *name* is freed; the actor's mailbox (and any other names for /// it) remain. A binding to a dead actor is reported as `None`. pub fn unregister(name: &str) -> Option { with_runtime(|inner| { let mut reg = inner.registry.lock(); let idx = reg.by_name.remove(name)?; match reg.by_index.get(&idx).map(|m| m.pid) { Some(pid) if live(inner, pid) => Some(pid), _ => None, } }) } /// Resolve `name` to its actor's `Sender` and deliver `msg`. The whole point /// of the rework: a name you can *send* to. /// /// Errors (message returned in every case): [`SendError::Unresolved`] if no /// live actor holds the name, [`SendError::NoChannel`] if that actor has no /// channel for `M`, [`SendError::Closed`] if its `M` channel's receiver is /// gone. Panics if called outside `Runtime::run()`. pub fn send(name: Name, msg: M) -> Result<(), SendError> { let key = name.as_str(); with_runtime(|inner| { // Resolve + clone the sender under the Leaf lock, then drop the lock // before sending (a send can unpark a receiver). let tx = { let mut reg = inner.registry.lock(); let idx = match reg.by_name.get(key) { Some(&i) => i, None => return Err(SendError::Unresolved(msg)), }; let pid = match reg.by_index.get(&idx).map(|m| m.pid) { Some(pid) => pid, None => { reg.by_name.remove(key); return Err(SendError::Unresolved(msg)); } }; if !live(inner, pid) { reg.prune(idx); return Err(SendError::Unresolved(msg)); } match reg.by_index.get(&idx).and_then(Mailbox::clone_sender::) { Some(tx) => tx, None => return Err(SendError::NoChannel(msg)), } }; tx.send(msg).map_err(|crate::channel::SendError(m)| SendError::Closed(m)) }) } /// Resolve a *raw* pid to its mailbox and deliver `msg` on the channel for `M`, /// with **no redirect**. The stored mailbox must be this exact incarnation /// (generation included) and still live; otherwise the actor this pid named is /// gone and the result is [`SendError::Dead`] — even when the slot now holds a /// different, live actor (which we leave untouched). Shared by [`send_to`] /// (typed, `M = A::Msg`, channel guaranteed on an installed actor) and /// [`send_dyn`] (explicit `M`, where `NoChannel` is a real outcome). fn send_to_pid( inner: &crate::runtime::RuntimeInner, pid: Pid, msg: M, ) -> Result<(), SendError> { // Resolve + clone the sender under the Leaf lock, then drop the lock before // sending (a send can unpark a receiver) — Leaf -> Channel, as name `send`. let tx = { let mut reg = inner.registry.lock(); match reg.by_index.get(&pid.index()).map(|m| m.pid) { // Exact incarnation, still alive: its `M` channel, or NoChannel. Some(stored) if stored == pid && live(inner, pid) => { match reg.by_index.get(&pid.index()).and_then(Mailbox::clone_sender::) { Some(tx) => tx, None => return Err(SendError::NoChannel(msg)), } } // Our incarnation's mailbox, but the actor has died: prune + Dead. Some(stored) if stored == pid => { reg.prune(pid.index()); return Err(SendError::Dead(msg)); } // A different incarnation (or nothing) occupies the slot: the actor // this pid named is gone. Do not disturb any newer occupant. _ => return Err(SendError::Dead(msg)), } }; tx.send(msg).map_err(|crate::channel::SendError(m)| SendError::Closed(m)) } /// Deliver `msg` to the exact actor named by `pid` — RFC 014 §4.2's direct, /// identity-bound addressing mode. Unlike name-addressed [`send`] there is **no /// redirect**: if that incarnation has died the message comes back as /// [`SendError::Dead`], even if its slot now holds a different actor. /// /// The message type is the actor's `A::Msg`, so on a live actor that has /// installed its inbox (via [`install`] or [`register`]) the channel is always /// present; [`SendError::NoChannel`] therefore means the actor is live but /// never published a `Pid`-reachable inbox. Panics if called outside /// `Runtime::run()`. pub fn send_to(pid: Pid, msg: A::Msg) -> Result<(), SendError> { with_runtime(|inner| send_to_pid::(inner, pid.erase(), msg)) } /// The explicit bare-pid escape hatch (RFC 014 §4.6): deliver `msg` of type `M` /// to `pid` when all you hold is an untyped [`Pid`] — a pid off a [`Down`], or /// out of a future `members()` — so the typed [`send_to`] is unavailable. /// /// This is the one send whose message type can genuinely be wrong: the actor /// may be live yet expose no channel for `M`, returning [`SendError::NoChannel`] /// (on the typed paths that downcast collapses to a `debug_assert`). It is /// named and documented as the fallible fallback so the typed `Pid` / /// `Name` paths stay the obvious default and an agentic caller reaches for a /// present primitive instead of inventing a workaround. Liveness is identical /// to [`send_to`]: identity-bound, no redirect, [`SendError::Dead`] once the /// addressed incarnation is gone. Panics if called outside `Runtime::run()`. /// /// [`Down`]: crate::Down pub fn send_dyn(pid: Pid, msg: M) -> Result<(), SendError> { with_runtime(|inner| send_to_pid::(inner, pid, msg)) }