//! Named pid registry. //! //! `register(name, pid)` binds a name to a live actor; `whereis(name)` looks //! it up; `name_of(pid)` is the inverse. Erlang semantics: at most one name //! per pid, at most one pid per name, registering over a live binding is an //! error. The registry is a *bimap* (two `HashMap`s kept in exact inverse //! under one lock) so both lookup directions are O(1) — `name_of` exists so //! supervisors, tracing, and diagnostics can label a pid without scanning. //! //! ## Cleanup is lazy //! //! There is no hook in `finalize_actor` and no name field in `Slot` (which //! would buy into the reset-in-three-places slot invariant). Instead every //! operation that touches a binding checks the bound pid's liveness via the //! generation-checked slot word — a `Pid` is `(index, generation)` and a //! generation is never reused, so a stale binding is *detectable*, never //! misdirected. Bindings to dead actors behave as absent and are pruned on //! contact: `whereis`/`name_of` return `None`, `register` treats the name as //! free. The cost is that a dead binding lingers until something looks at it; //! the payoff is zero coupling to the actor lifecycle. //! //! ## Locking //! //! One `RawMutex` (Leaf class) in `RuntimeInner`. Liveness checks under it //! read only the atomic slot state word — no second lock is ever taken, so //! the leaf rule holds trivially. use crate::pid::Pid; use crate::scheduler::with_runtime; 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 pid already has a (live) registered name; one name per pid. PidAlreadyRegistered { name: String }, /// The pid does not refer to a live actor. 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::PidAlreadyRegistered { name } => { write!(f, "pid is already registered as {name:?}") } RegisterError::NoProc => write!(f, "pid does not refer to a live actor"), } } } impl std::error::Error for RegisterError {} /// The bimap. Invariant (held under the registry lock): `by_name` and /// `by_pid` are exact inverses of each other at all times. pub(crate) struct Registry { by_name: HashMap, by_pid: HashMap, } impl Registry { pub(crate) fn new() -> Self { Self { by_name: HashMap::new(), by_pid: HashMap::new() } } fn remove_binding(&mut self, name: &str, pid: Pid) { self.by_name.remove(name); self.by_pid.remove(&pid); debug_assert_eq!(self.by_name.len(), self.by_pid.len(), "bimap out of sync"); } fn insert_binding(&mut self, name: String, pid: Pid) { self.by_pid.insert(pid, name.clone()); self.by_name.insert(name, pid); debug_assert_eq!(self.by_name.len(), self.by_pid.len(), "bimap out of sync"); } } /// 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)) } /// Bind `name` to the live actor `pid`. /// /// Fails with [`RegisterError::NoProc`] if `pid` is not live, /// [`RegisterError::NameTaken`] if the name is bound to a *live* actor /// (a binding to a dead actor is evicted and the name treated as free), and /// [`RegisterError::PidAlreadyRegistered`] if `pid` already has a name. /// /// Panics if called outside `Runtime::run()`. pub fn register(name: impl Into, pid: Pid) -> Result<(), RegisterError> { let name = name.into(); with_runtime(|inner| { let mut reg = inner.registry.lock(); if !live(inner, pid) { return Err(RegisterError::NoProc); } let prior = reg.by_name.get(&name).copied(); if let Some(holder) = prior { if holder == pid { return Ok(()); // already exactly this binding: idempotent } if live(inner, holder) { return Err(RegisterError::NameTaken { holder }); } // Stale binding: the holder died. Evict and treat the name as free. reg.remove_binding(&name, holder); } if let Some(existing) = reg.by_pid.get(&pid) { // `pid` is live (checked above) and pids are never reused, so this // binding is necessarily current — one name per pid. return Err(RegisterError::PidAlreadyRegistered { name: existing.clone() }); } reg.insert_binding(name, pid); Ok(()) }) } /// Look up the pid bound to `name`. `None` if unbound, or if the bound actor /// is 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 pid = *reg.by_name.get(name)?; if live(inner, pid) { Some(pid) } else { reg.remove_binding(name, pid); None } }) } /// The inverse lookup: the name `pid` is registered under, if any. `None` if /// unregistered or no longer live (pruning the stale binding). pub fn name_of(pid: Pid) -> Option { with_runtime(|inner| { let mut reg = inner.registry.lock(); let name = reg.by_pid.get(&pid)?.clone(); if live(inner, pid) { Some(name) } else { reg.remove_binding(&name, pid); None } }) } /// Remove the binding for `name`, returning the pid it was bound to if that /// actor is still live. A binding to a dead actor is pruned and reported as /// `None`, consistent with [`whereis`]. pub fn unregister(name: &str) -> Option { with_runtime(|inner| { let mut reg = inner.registry.lock(); let pid = *reg.by_name.get(name)?; reg.remove_binding(name, pid); if live(inner, pid) { Some(pid) } else { None } }) }