docs(registry): user-facing rewrite of the name registry
Replace the 'what changed' diff-against-a-prior-design framing with a plain explanation of what the registry is for (naming an actor so others can find and message it by name) and a compiling doctest (register/whereis/send/unregister). Cut all RFC/decision-number/bug-id references and em-dashes; move type-erasure and locking-discipline detail into an Implementation notes section for contributors.
This commit is contained in:
+242
-167
@@ -1,55 +1,108 @@
|
|||||||
//! Named mailbox registry — resolve a name (or pid) to a *messageable* actor.
|
|
||||||
|
//! Give an actor a name so other actors can find it and message it.
|
||||||
//!
|
//!
|
||||||
//! ## What changed (RFC 014)
|
//! Without the registry, the only way to reach an actor is to already be
|
||||||
|
//! holding its [`Pid`], usually because you spawned it yourself or someone
|
||||||
|
//! passed it to you. That is fine for a worker you just created, but it does
|
||||||
|
//! not work for a well-known service that arbitrary parts of your program
|
||||||
|
//! need to find independently, like a logger, a config store, or a
|
||||||
|
//! connection pool. The registry solves this: an actor claims a name once,
|
||||||
|
//! and from then on any other actor can look that name up, or send to it
|
||||||
|
//! directly, without ever having been handed a `Pid`.
|
||||||
//!
|
//!
|
||||||
//! 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
|
//! use smarm::{channel, register, run, send, spawn, unregister, whereis, Name};
|
||||||
//! delivery endpoint. This rework makes resolution yield something messageable.
|
|
||||||
//!
|
//!
|
||||||
//! Two facts shape the structure:
|
//! const COUNTER: Name<u64> = Name::new("counter");
|
||||||
//!
|
//!
|
||||||
//! 1. **A name resolves to a single actor.** Many actors under one label is
|
//! run(|| {
|
||||||
//! what *process groups* (`pg`) are for; the registry is one-name-one-actor
|
//! let (ready_tx, ready_rx) = channel::<()>();
|
||||||
//! (several names *may* point at the same actor).
|
//! let (tx, rx) = channel::<u64>();
|
||||||
//! 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
|
//! let worker = spawn(move || {
|
||||||
//! channel for message type M`. A `Name<Cmd>` and a `Name<Admin>` on the *same*
|
//! // Claim the name for this actor's inbox. Any actor holding
|
||||||
//! actor select *different* channels purely by their type parameter, so
|
//! // `COUNTER` can now reach this one by name.
|
||||||
//! capability separation (RFC 014 §4.7) needs no extra machinery.
|
//! register(COUNTER, tx).unwrap();
|
||||||
|
//! ready_tx.send(()).unwrap();
|
||||||
|
//! assert_eq!(rx.recv().unwrap(), 42);
|
||||||
|
//! });
|
||||||
//!
|
//!
|
||||||
//! ## Type erasure is contained
|
//! ready_rx.recv().unwrap(); // wait for the worker to register
|
||||||
//!
|
//!
|
||||||
//! Each stored channel is a `Box<dyn Any + Send>` that is concretely a
|
//! // Look the name up, or just send to it directly.
|
||||||
//! `Sender<M>`, filed under `TypeId::of::<M>()`. A resolve for `M` looks up
|
//! assert_eq!(whereis("counter"), Some(worker.pid()));
|
||||||
//! that exact `TypeId` and downcasts to `Sender<M>` — keyed by the very type we
|
//! send(COUNTER, 42).unwrap();
|
||||||
//! 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)
|
//! worker.join().unwrap();
|
||||||
//!
|
//!
|
||||||
//! As before, there is no `finalize` hook and no name field on the slot. Every
|
//! // The name dies with the actor: nobody holds it anymore.
|
||||||
//! operation that touches a binding checks the target pid's liveness via the
|
//! assert_eq!(whereis("counter"), None);
|
||||||
//! 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
|
//! ## Names carry a message type
|
||||||
//!
|
//!
|
||||||
//! One `RawMutex` (Leaf class) in `RuntimeInner`, exactly like the old
|
//! A [`Name<M>`] is a plain string plus a type parameter `M`: the message
|
||||||
//! registry. The fold (name index *and* handles under the one lock) is what
|
//! type that name expects to receive. [`Name::new`] is `const`, so the usual
|
||||||
//! keeps a name-addressed `send` on a single Leaf — `raw_mutex` panics on a
|
//! pattern is a module-level constant like `COUNTER` above, shared by every
|
||||||
//! second Leaf acquired while one is held. The send path clones the `Sender`
|
//! caller. The type parameter means a name is only ever sent the kind of
|
||||||
//! **under** the Leaf lock (a `Sender::clone` takes a Channel lock, permitted
|
//! message it was declared for. If two different constants share the same
|
||||||
//! under a Leaf), then **releases** the Leaf and only *then* sends — a send can
|
//! string but have different message types, they still address two
|
||||||
//! unpark a receiver, and wakeup-bearing work runs outside the Leaf. Order is
|
//! independent channels on the same actor: registering both just gives that
|
||||||
//! **Leaf -> Channel**, as `pg`/`finalize`.
|
//! actor two ways to be reached, one per message type. This is how you give
|
||||||
|
//! one actor a "public" channel and a separate, differently-typed "admin"
|
||||||
|
//! channel under related names, without inventing an enum to merge them.
|
||||||
|
//!
|
||||||
|
//! ## One actor per name, looked up fresh every time
|
||||||
|
//!
|
||||||
|
//! A name always points at exactly one actor at a time (contrast a *process
|
||||||
|
//! group*, from the [`pg`](crate::pg) module, which is one name mapping to
|
||||||
|
//! many actors). Unlike a plain [`Pid`], which names one specific actor
|
||||||
|
//! forever and stops working the moment that actor dies, a name is
|
||||||
|
//! re-resolved on every [`send`]: if the actor holding it dies and a new one
|
||||||
|
//! registers under the same name, the next `send` reaches the new holder
|
||||||
|
//! automatically. Use a name for a long-lived service whose exact identity
|
||||||
|
//! you do not want to track by hand; use a `Pid` when you already have one
|
||||||
|
//! and want to talk to that exact actor.
|
||||||
|
//!
|
||||||
|
//! ## Registration ends when the actor does
|
||||||
|
//!
|
||||||
|
//! There is no separate step to clean up a name when its actor exits: dying
|
||||||
|
//! is enough. The next operation that touches a dead binding (a [`whereis`],
|
||||||
|
//! a [`send`], or another actor's [`register`] of the same name) notices the
|
||||||
|
//! actor is gone and clears the stale entry as a side effect, so the name
|
||||||
|
//! becomes free again. [`unregister`] is only for a live actor voluntarily
|
||||||
|
//! giving up a name it no longer wants; nothing has to call it on the way
|
||||||
|
//! out.
|
||||||
|
//!
|
||||||
|
//! ## Implementation notes
|
||||||
|
//!
|
||||||
|
//! These details matter if you are working on smarm itself; they are not
|
||||||
|
//! part of the public contract.
|
||||||
|
//!
|
||||||
|
//! Internally, each live actor that has published at least one channel owns
|
||||||
|
//! a `Mailbox`: its pid plus a set of typed channels, keyed by the message
|
||||||
|
//! type's `TypeId`. A stored channel is a `Box<dyn Any + Send>` that
|
||||||
|
//! is concretely a `Sender<M>`; resolving for `M` looks up that exact
|
||||||
|
//! `TypeId` and downcasts, so the downcast cannot fail on correct data (a
|
||||||
|
//! failure would be a bug in the registry itself, checked in debug builds).
|
||||||
|
//! Registering a name therefore means: find or create the actor's mailbox,
|
||||||
|
//! insert the channel under its type, and point the name at the actor's pid.
|
||||||
|
//!
|
||||||
|
//! There is no callback when an actor exits. Every operation that touches a
|
||||||
|
//! binding checks the target pid's liveness directly against the scheduler's
|
||||||
|
//! slot table (which also tracks a generation counter, so a dead actor's
|
||||||
|
//! reused slot index is never mistaken for the same actor). A binding to a
|
||||||
|
//! dead actor is treated as absent and dropped right there. This keeps the
|
||||||
|
//! registry decoupled from actor teardown, at the cost of a dead binding
|
||||||
|
//! lingering until something happens to look at it.
|
||||||
|
//!
|
||||||
|
//! The whole registry (both the name index and the per-actor mailboxes) sits
|
||||||
|
//! behind one lock, which is what lets a name-addressed [`send`] resolve and
|
||||||
|
//! clone the target's sender in a single critical section. The sender is
|
||||||
|
//! cloned while that lock is held, then the lock is released before the
|
||||||
|
//! actual send, since delivering a message can wake a parked receiver and
|
||||||
|
//! that wakeup work should not run while the registry is locked.
|
||||||
|
|
||||||
use crate::channel::Sender;
|
use crate::channel::Sender;
|
||||||
use crate::pid::{Addressable, Name, Pid};
|
use crate::pid::{Addressable, Name, Pid};
|
||||||
@@ -80,28 +133,33 @@ impl std::fmt::Display for RegisterError {
|
|||||||
|
|
||||||
impl std::error::Error for RegisterError {}
|
impl std::error::Error for RegisterError {}
|
||||||
|
|
||||||
/// Why a name-addressed [`send`] did not deliver. Carries the message back so
|
/// Why a send did not deliver. Every variant carries the undelivered message
|
||||||
/// the caller never loses it (mirrors [`crate::channel::SendError`]).
|
/// back, mirroring [`crate::channel::SendError`], so a failed send never
|
||||||
|
/// silently drops what you tried to send.
|
||||||
///
|
///
|
||||||
/// `Debug`/`Display` are hand-written so neither demands `M: Debug` — the
|
/// `Debug` and `Display` are hand-written so neither requires `M: Debug`,
|
||||||
/// payload is returned, not printed.
|
/// since the payload is handed back to you, not printed.
|
||||||
pub enum SendError<M> {
|
pub enum SendError<M> {
|
||||||
/// No live actor is currently registered under this name. Name-addressed
|
/// No live actor is currently registered under this name. Returned only
|
||||||
/// [`send`] only; the pid-addressed counterpart is [`SendError::Dead`].
|
/// by name-addressed [`send`]; the pid-addressed counterpart of "nothing
|
||||||
|
/// there" is [`SendError::Dead`].
|
||||||
Unresolved(M),
|
Unresolved(M),
|
||||||
/// The pid-addressed actor is no longer the live incarnation this pid names
|
/// The actor this pid identifies has died, even if its slot has since
|
||||||
/// — it has died, even if its slot now holds a *different* actor (a direct
|
/// been taken over by a different, live actor. A direct `Pid<A>` send
|
||||||
/// `Pid<A>` send never redirects; contrast name-addressed [`send`]). Pid
|
/// never redirects to that new occupant; contrast name-addressed
|
||||||
/// paths ([`send_to`] / [`send_dyn`]) only.
|
/// [`send`], which would reach it. Returned by the pid-addressed sends,
|
||||||
|
/// [`send_to`] and [`send_dyn`].
|
||||||
Dead(M),
|
Dead(M),
|
||||||
/// The actor is live but exposes no channel for this message type.
|
/// The actor is live but has not published a channel for this message
|
||||||
|
/// type.
|
||||||
NoChannel(M),
|
NoChannel(M),
|
||||||
/// The actor's channel for this message type is closed (its receiver is gone).
|
/// The actor's channel for this message type is closed (its receiver has
|
||||||
|
/// been dropped).
|
||||||
Closed(M),
|
Closed(M),
|
||||||
/// No live member to deliver to — a [`dispatch`](crate::dispatch) over an
|
/// No live member was available to deliver to: returned by
|
||||||
/// empty (or all-dead) process group. Group-addressed dispatch only; the
|
/// [`dispatch`](crate::dispatch) when the target process group is empty
|
||||||
/// name-addressed counterpart is [`SendError::Unresolved`]. The message is
|
/// or every member in it has died. The name-addressed counterpart of
|
||||||
/// handed back undelivered.
|
/// this case is [`SendError::Unresolved`].
|
||||||
NoMember(M),
|
NoMember(M),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,9 +208,9 @@ impl<M> std::error::Error for SendError<M> {}
|
|||||||
|
|
||||||
/// A registry-stored channel, type-erased over its message type. The stored
|
/// A registry-stored channel, type-erased over its message type. The stored
|
||||||
/// object must serve two readers: `clone_sender` (downcast back to the concrete
|
/// object must serve two readers: `clone_sender` (downcast back to the concrete
|
||||||
/// `Sender<M>`) and the RFC 016 snapshot (queued length without knowing `M`).
|
/// `Sender<M>`) and the runtime introspection snapshot (queued length without
|
||||||
/// A bare `Box<dyn Any>` gives the first but not the second, so we erase behind
|
/// knowing `M`). A bare `Box<dyn Any>` gives the first but not the second, so
|
||||||
/// this small trait instead.
|
/// we erase behind this small trait instead.
|
||||||
trait ErasedSender: Send {
|
trait ErasedSender: Send {
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
fn queued_len(&self) -> usize;
|
fn queued_len(&self) -> usize;
|
||||||
@@ -169,7 +227,7 @@ impl<M: Send + 'static> ErasedSender for Sender<M> {
|
|||||||
|
|
||||||
/// One typed channel of an actor, type-erased. Concretely a `Sender<M>` filed
|
/// One typed channel of an actor, type-erased. Concretely a `Sender<M>` filed
|
||||||
/// under `TypeId::of::<M>()`; `msg_type` is `type_name::<M>()`, kept for
|
/// under `TypeId::of::<M>()`; `msg_type` is `type_name::<M>()`, kept for
|
||||||
/// observers (RFC 014 §4.5) and as the debug cross-check on the downcast.
|
/// observability tooling and as the debug cross-check on the downcast.
|
||||||
struct Channel {
|
struct Channel {
|
||||||
sender: Box<dyn ErasedSender>,
|
sender: Box<dyn ErasedSender>,
|
||||||
msg_type: &'static str,
|
msg_type: &'static str,
|
||||||
@@ -204,7 +262,7 @@ impl Mailbox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Per-actor registry view handed to RFC 016 introspection: registered names
|
/// Per-actor registry view handed to runtime introspection: registered names
|
||||||
/// and summed mailbox depth, tagged with the mailbox's `pid` so a stale
|
/// and summed mailbox depth, tagged with the mailbox's `pid` so a stale
|
||||||
/// incarnation can be filtered against the slab. Covers only *published*
|
/// incarnation can be filtered against the slab. Covers only *published*
|
||||||
/// channels (`register` / `install` / `spawn_addr` / gen_server start); an
|
/// channels (`register` / `install` / `spawn_addr` / gen_server start); an
|
||||||
@@ -219,17 +277,18 @@ pub(crate) struct MailboxInfo {
|
|||||||
/// The directory. Invariant (held under the registry lock): every value in
|
/// The directory. Invariant (held under the registry lock): every value in
|
||||||
/// `by_name` is the full [`Pid`] (index *and* generation) of an actor that
|
/// `by_name` is the full [`Pid`] (index *and* generation) of an actor that
|
||||||
/// published a [`Mailbox`] into `by_index` at registration time. Stale entries
|
/// published a [`Mailbox`] into `by_index` at registration time. Stale entries
|
||||||
/// (dead holders — including holders whose slot has since been re-tenanted by
|
/// (dead holders, including holders whose slot has since been re-tenanted by
|
||||||
/// a different actor) violate nothing — they are pruned on contact, and the
|
/// a different actor) violate nothing: they are pruned on contact, and the
|
||||||
/// generation makes "dead" decidable even after slot reuse.
|
/// generation makes "dead" decidable even after slot reuse.
|
||||||
pub(crate) struct Registry {
|
pub(crate) struct Registry {
|
||||||
/// `pid.index() -> the actor's mailbox`. The handle store.
|
/// `pid.index() -> the actor's mailbox`. The handle store.
|
||||||
by_index: HashMap<u32, Mailbox>,
|
by_index: HashMap<u32, Mailbox>,
|
||||||
/// `name -> holder pid`. Several names may map to one actor. The full pid
|
/// `name -> holder pid`. Several names may map to one actor. The full pid
|
||||||
/// (not just the index) is load-bearing: an index alone cannot tell a dead
|
/// (not just the index) is load-bearing: an index alone cannot tell a dead
|
||||||
/// holder from the live actor now tenanting its recycled slot, which made
|
/// holder from the live actor now tenanting its recycled slot. Comparing
|
||||||
/// such a name read as live-held — unresolvable *and* unregisterable — and
|
/// only the index would make such a name read as live-held (unresolvable
|
||||||
/// would misdeliver to a same-typed tenant (soak20 signature 2).
|
/// and unregisterable at once) and could misdeliver to whatever new,
|
||||||
|
/// same-typed actor now sits in that slot.
|
||||||
by_name: HashMap<&'static str, Pid>,
|
by_name: HashMap<&'static str, Pid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,10 +297,10 @@ impl Registry {
|
|||||||
Self { by_index: HashMap::new(), by_name: HashMap::new() }
|
Self { by_index: HashMap::new(), by_name: HashMap::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Drop a dead holder's artifacts: every name bound to it, and its mailbox
|
/// Drop a dead holder's artifacts: every name bound to it, and its
|
||||||
/// — but only while the mailbox is still *its own*. A recycled slot's
|
/// mailbox, but only while the mailbox is still *its own*. A recycled
|
||||||
/// mailbox belongs to the live tenant (publish replaces it wholesale on
|
/// slot's mailbox belongs to the live tenant (publish replaces it
|
||||||
/// pid mismatch) and is left untouched.
|
/// wholesale on pid mismatch) and is left untouched.
|
||||||
fn prune_holder(&mut self, holder: Pid) {
|
fn prune_holder(&mut self, holder: Pid) {
|
||||||
self.by_name.retain(|_, p| *p != holder);
|
self.by_name.retain(|_, p| *p != holder);
|
||||||
if self.by_index.get(&holder.index()).is_some_and(|mb| mb.pid == holder) {
|
if self.by_index.get(&holder.index()).is_some_and(|mb| mb.pid == holder) {
|
||||||
@@ -249,17 +308,16 @@ impl Registry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// RFC 016 snapshot input: per-slot-index registry view — the actor's
|
/// Runtime introspection input: per-slot-index registry view, giving the
|
||||||
/// registered names (inverted from `by_name`) and its mailbox depth (queued
|
/// actor's registered names (inverted from `by_name`) and its mailbox
|
||||||
/// messages summed across every published typed channel). Built in one pass
|
/// depth (queued messages summed across every published typed channel).
|
||||||
/// under the registry Leaf; the per-channel `queued_len` takes a Channel
|
/// Carries each mailbox's full `pid` so the caller can discard a stale
|
||||||
/// lock, legal under the Leaf (Leaf → Channel). Carries each mailbox's full
|
/// incarnation's entry against the slab's live generation. Names are
|
||||||
/// `pid` so the caller can discard a stale incarnation's entry against the
|
/// matched to mailboxes by *full pid*, so a stale name (dead holder)
|
||||||
/// slab's live generation. Names are matched to mailboxes by *full pid*, so
|
/// still annotates the corpse's own mailbox if that survives, but never a
|
||||||
/// a stale name (dead holder) still annotates the corpse's own mailbox if
|
/// recycled slot's new tenant; names that attach to no mailbox are
|
||||||
/// that survives, but never a recycled slot's new tenant; names that attach
|
/// dropped, since that violates no invariant and they get pruned on next
|
||||||
/// to no mailbox are dropped — they violate no invariant and get pruned on
|
/// contact.
|
||||||
/// next contact.
|
|
||||||
pub(crate) fn introspect_map(&self) -> HashMap<u32, MailboxInfo> {
|
pub(crate) fn introspect_map(&self) -> HashMap<u32, MailboxInfo> {
|
||||||
let mut names: HashMap<Pid, Vec<&'static str>> = HashMap::new();
|
let mut names: HashMap<Pid, Vec<&'static str>> = HashMap::new();
|
||||||
for (&name, &pid) in &self.by_name {
|
for (&name, &pid) in &self.by_name {
|
||||||
@@ -282,8 +340,9 @@ impl Registry {
|
|||||||
|
|
||||||
/// Single-actor form of [`introspect_map`](Self::introspect_map): the
|
/// Single-actor form of [`introspect_map`](Self::introspect_map): the
|
||||||
/// registry view for one slot index, or `None` if no mailbox is published
|
/// 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
|
/// there. Used by the runtime's per-actor introspection so its cost stays
|
||||||
/// actor rather than locking every channel in the runtime.
|
/// proportional to the one actor rather than locking every channel in the
|
||||||
|
/// runtime.
|
||||||
pub(crate) fn introspect_one(&self, idx: u32) -> Option<MailboxInfo> {
|
pub(crate) fn introspect_one(&self, idx: u32) -> Option<MailboxInfo> {
|
||||||
let mb = self.by_index.get(&idx)?;
|
let mb = self.by_index.get(&idx)?;
|
||||||
let depth: usize = mb.channels.values().map(|c| c.sender.queued_len()).sum();
|
let depth: usize = mb.channels.values().map(|c| c.sender.queued_len()).sum();
|
||||||
@@ -301,14 +360,18 @@ fn live(inner: &crate::runtime::RuntimeInner, pid: Pid) -> bool {
|
|||||||
inner.slot_at(pid).is_some_and(|s| s.is_live_for(pid))
|
inner.slot_at(pid).is_some_and(|s| s.is_live_for(pid))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Publish the current actor's `Sender<M>` under `name`, capturing the channel
|
/// Give the current actor's channel a name, so other actors can find and
|
||||||
/// so the name becomes messageable. Idempotent for the same `(name, type)`;
|
/// message it by that name instead of needing its [`Pid`].
|
||||||
/// 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*
|
/// Calling this again with the same `(name, type)` from the same actor is
|
||||||
/// live actor (a binding to a dead actor is pruned and the name treated as
|
/// harmless. Registering a *second* message type under the same (or a
|
||||||
/// free). Panics if called outside `Runtime::run()`.
|
/// different) name from the same actor just adds another typed channel to
|
||||||
|
/// that actor's mailbox; it does not replace the first.
|
||||||
|
///
|
||||||
|
/// Fails with [`RegisterError::NameTaken`] if the name is currently held by a
|
||||||
|
/// *different* live actor. A name held by an actor that has since died is not
|
||||||
|
/// considered taken: it is quietly reclaimed and handed to you. Panics if
|
||||||
|
/// called outside [`run`](crate::run).
|
||||||
pub fn register<M: Send + 'static>(name: Name<M>, tx: Sender<M>) -> Result<(), RegisterError> {
|
pub fn register<M: Send + 'static>(name: Name<M>, tx: Sender<M>) -> Result<(), RegisterError> {
|
||||||
register_with(self_pid(), name.as_str(), tx)
|
register_with(self_pid(), name.as_str(), tx)
|
||||||
}
|
}
|
||||||
@@ -316,8 +379,8 @@ pub fn register<M: Send + 'static>(name: Name<M>, tx: Sender<M>) -> Result<(), R
|
|||||||
/// Bind `name` to `pid`'s mailbox and publish `tx` under `M`'s [`TypeId`], for
|
/// 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
|
/// an explicit (already-live) actor rather than `self`. The shared core of
|
||||||
/// [`register`] (which passes `self_pid()`) and the parent-side server-name
|
/// [`register`] (which passes `self_pid()`) and the parent-side server-name
|
||||||
/// bind in `gen_server` (which names a freshly spawned server before its body
|
/// 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
|
/// has run, so the name resolves the instant `start()` returns. Same collision
|
||||||
/// rules and lock discipline as `register`.
|
/// rules and lock discipline as `register`.
|
||||||
pub(crate) fn register_with<M: Send + 'static>(
|
pub(crate) fn register_with<M: Send + 'static>(
|
||||||
me: Pid,
|
me: Pid,
|
||||||
@@ -337,8 +400,8 @@ pub(crate) fn register_with<M: Send + 'static>(
|
|||||||
} else {
|
} else {
|
||||||
// Dead holder: free the name (and its other stale artifacts).
|
// Dead holder: free the name (and its other stale artifacts).
|
||||||
// Liveness is judged against the *stored* pid, generation
|
// Liveness is judged against the *stored* pid, generation
|
||||||
// included — a recycled slot's live tenant no longer makes a
|
// included, so a recycled slot's live tenant no longer makes a
|
||||||
// dead name read as taken (soak20 signature 2).
|
// dead name read as taken.
|
||||||
reg.prune_holder(holder);
|
reg.prune_holder(holder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,14 +430,14 @@ fn publish_channel<M: Send + 'static>(reg: &mut Registry, me: Pid, tx: Sender<M>
|
|||||||
|
|
||||||
/// Publish the current actor's `Sender<A::Msg>` into its mailbox **without**
|
/// Publish the current actor's `Sender<A::Msg>` into its mailbox **without**
|
||||||
/// binding a name, and hand back the typed [`Pid<A>`] that addresses this
|
/// binding a name, and hand back the typed [`Pid<A>`] that addresses this
|
||||||
/// actor directly. This is the opt-in, lazy install of RFC 014 §5: an actor
|
/// actor directly.
|
||||||
/// that wants to be reachable by a direct, identity-bound [`Pid<A>`] (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
|
/// This is for an actor that wants to be reachable directly by its pid,
|
||||||
/// live actor inside `run()`, so this is infallible. Panics if called outside
|
/// rather than only through a re-resolving [`Name`]: call this once with your
|
||||||
/// `Runtime::run()`.
|
/// inbox sender, then hand the returned `Pid<A>` to whoever should be able to
|
||||||
|
/// message you. Unlike [`register`] there is no name to collide on, and the
|
||||||
|
/// current actor is always live while inside `run()`, so this cannot fail.
|
||||||
|
/// Panics if called outside [`run`](crate::run).
|
||||||
pub fn install<A: Addressable>(tx: Sender<A::Msg>) -> Pid<A> {
|
pub fn install<A: Addressable>(tx: Sender<A::Msg>) -> Pid<A> {
|
||||||
let me = self_pid();
|
let me = self_pid();
|
||||||
with_runtime(|inner| {
|
with_runtime(|inner| {
|
||||||
@@ -390,11 +453,12 @@ pub fn install<A: Addressable>(tx: Sender<A::Msg>) -> Pid<A> {
|
|||||||
/// Publish `tx` into `pid`'s mailbox under `M`'s [`TypeId`], for an explicit
|
/// Publish `tx` into `pid`'s mailbox under `M`'s [`TypeId`], for an explicit
|
||||||
/// (freshly minted, already-live) actor rather than `self`. The parent-side
|
/// (freshly minted, already-live) actor rather than `self`. The parent-side
|
||||||
/// half of [`spawn_addr`](crate::spawn_addr): the spawner makes the inbox and
|
/// half of [`spawn_addr`](crate::spawn_addr): the spawner makes the inbox and
|
||||||
/// publishes the sender here *before* handing back the `Pid<A>`, so an immediate
|
/// publishes the sender here *before* handing back the `Pid<A>`, so an
|
||||||
/// `send_to` on the returned pid always resolves — the address is live the
|
/// immediate `send_to` on the returned pid always resolves. The address is
|
||||||
/// instant the caller holds it, with no dependence on the body having run yet.
|
/// live the instant the caller holds it, with no dependence on the spawned
|
||||||
|
/// actor's body having run yet.
|
||||||
///
|
///
|
||||||
/// Caller guarantees `pid` is the just-installed actor (Queued, this exact
|
/// Caller guarantees `pid` is the just-installed actor (queued, this exact
|
||||||
/// incarnation); `publish_channel` replaces any stale leftover at the slot.
|
/// incarnation); `publish_channel` replaces any stale leftover at the slot.
|
||||||
pub(crate) fn install_for<M: Send + 'static>(pid: Pid, tx: Sender<M>) {
|
pub(crate) fn install_for<M: Send + 'static>(pid: Pid, tx: Sender<M>) {
|
||||||
with_runtime(|inner| {
|
with_runtime(|inner| {
|
||||||
@@ -404,8 +468,9 @@ pub(crate) fn install_for<M: Send + 'static>(pid: Pid, tx: Sender<M>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The single actor currently registered under `name`, or `None` if unbound or
|
/// Look up which actor currently holds `name`, if any. Returns `None` if the
|
||||||
/// no longer live (the stale binding is pruned on the way out).
|
/// name is unbound, or if it was bound to an actor that has since died (the
|
||||||
|
/// stale binding is cleared as a side effect of this call).
|
||||||
pub fn whereis(name: &str) -> Option<Pid> {
|
pub fn whereis(name: &str) -> Option<Pid> {
|
||||||
with_runtime(|inner| {
|
with_runtime(|inner| {
|
||||||
let mut reg = inner.registry.lock();
|
let mut reg = inner.registry.lock();
|
||||||
@@ -421,34 +486,37 @@ pub fn whereis(name: &str) -> Option<Pid> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve `name` to a *typed* [`Pid<A>`] — the identity-bound counterpart of
|
/// Like [`whereis`], but returns a *typed* [`Pid<A>`] instead of a bare
|
||||||
/// [`whereis`] (RFC 014 §4.4). Recovers the compile-checked
|
/// [`Pid`], so a follow-up [`send_to`] is compile-checked instead of needing
|
||||||
/// [`send_to`] path from a durable name: looks the name up,
|
/// the untyped [`send_dyn`] escape hatch. `None` if the name is unbound or its
|
||||||
/// then re-types the erased pid as `Pid<A>` via the unchecked
|
/// holder has died.
|
||||||
/// [`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()`.
|
/// The type `A` is not checked against what the name's holder actually
|
||||||
|
/// published: if you pick the wrong `A`, this still succeeds, but the next
|
||||||
|
/// send against the returned pid degrades to [`SendError::NoChannel`] rather
|
||||||
|
/// than reaching the wrong actor or the wrong channel.
|
||||||
|
///
|
||||||
|
/// Panics if called outside [`run`](crate::run).
|
||||||
pub fn lookup_as<A: Addressable>(name: &str) -> Option<Pid<A>> {
|
pub fn lookup_as<A: Addressable>(name: &str) -> Option<Pid<A>> {
|
||||||
whereis(name).map(crate::pid::assert_type::<A>)
|
whereis(name).map(crate::pid::assert_type::<A>)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve `name` to its actor's pid and a cloned `Sender<M>`, under the Leaf
|
/// Resolve `name` to its actor's pid and a cloned `Sender<M>`, all under one
|
||||||
/// lock (clone-under-lock, then release). The crate-internal building block for
|
/// lock acquisition. The crate-internal building block for `gen_server`'s
|
||||||
/// `gen_server`'s by-name addressing: a named server publishes its inbox as a
|
/// by-name addressing: a named server publishes its inbox as a
|
||||||
/// `Sender<Envelope<G>>` (via [`register_with`]), and `whereis_server` / `call`
|
/// `Sender<Envelope<G>>` (via [`register_with`]), and the server's `call` /
|
||||||
/// / `cast` recover that exact typed sender here to rebuild a `GenServerRef<G>`.
|
/// `cast` / `whereis_server` recover that exact typed sender here to rebuild a
|
||||||
/// `None` if unbound, dead (pruned on the way out), or holding no `M` channel.
|
/// `GenServerRef<G>`. `None` if unbound, dead (pruned on the way out), or
|
||||||
|
/// holding no `M` channel.
|
||||||
pub(crate) fn resolve_named_sender<M: Send + 'static>(name: &str) -> Option<(Pid, Sender<M>)> {
|
pub(crate) fn resolve_named_sender<M: Send + 'static>(name: &str) -> Option<(Pid, Sender<M>)> {
|
||||||
with_runtime(|inner| {
|
with_runtime(|inner| {
|
||||||
let mut reg = inner.registry.lock();
|
let mut reg = inner.registry.lock();
|
||||||
let pid = *reg.by_name.get(name)?;
|
let pid = *reg.by_name.get(name)?;
|
||||||
if !live(inner, pid) {
|
if !live(inner, pid) {
|
||||||
// Stored-pid liveness, generation included: a name whose holder
|
// Stored-pid liveness, generation included: a name whose holder
|
||||||
// died is pruned (heals) even if the slot has a new tenant —
|
// died is pruned (heals) even if the slot has a new tenant.
|
||||||
// previously the tenant's mailbox made the name unresolvable
|
// Otherwise the tenant's mailbox would make the name unresolvable
|
||||||
// *without* pruning, wedging it for the tenant's lifetime.
|
// without pruning, wedging it for the tenant's lifetime.
|
||||||
reg.prune_holder(pid);
|
reg.prune_holder(pid);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -459,9 +527,10 @@ pub(crate) fn resolve_named_sender<M: Send + 'static>(name: &str) -> Option<(Pid
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the binding for `name`, returning the actor it pointed at if still
|
/// Give up a name. Returns the actor it pointed at, if that actor was still
|
||||||
/// live. Only the *name* is freed; the actor's mailbox (and any other names for
|
/// live. Only the *name* is freed; the actor's mailbox (and any other names
|
||||||
/// it) remain. A binding to a dead actor is reported as `None`.
|
/// bound to it) are unaffected. A binding to an already-dead actor reports
|
||||||
|
/// `None`, since there was nothing live to release.
|
||||||
pub fn unregister(name: &str) -> Option<Pid> {
|
pub fn unregister(name: &str) -> Option<Pid> {
|
||||||
with_runtime(|inner| {
|
with_runtime(|inner| {
|
||||||
let mut reg = inner.registry.lock();
|
let mut reg = inner.registry.lock();
|
||||||
@@ -470,18 +539,21 @@ pub fn unregister(name: &str) -> Option<Pid> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve `name` to its actor's `Sender<M>` and deliver `msg`. The whole point
|
/// Look `name` up and deliver `msg` to whichever actor currently holds it.
|
||||||
/// of the rework: a name you can *send* to.
|
/// This is the point of naming an actor: a name you can send a message to
|
||||||
|
/// directly, without a separate lookup step.
|
||||||
///
|
///
|
||||||
/// Errors (message returned in every case): [`SendError::Unresolved`] if no
|
/// On failure the message comes back to you, wrapped in the [`SendError`]
|
||||||
/// live actor holds the name, [`SendError::NoChannel`] if that actor has no
|
/// variant that explains why: [`SendError::Unresolved`] if no live actor
|
||||||
/// channel for `M`, [`SendError::Closed`] if its `M` channel's receiver is
|
/// currently holds the name, [`SendError::NoChannel`] if the actor that holds
|
||||||
/// gone. Panics if called outside `Runtime::run()`.
|
/// it never published a channel for `M`, or [`SendError::Closed`] if it
|
||||||
|
/// published one but has since dropped the receiving end. Panics if called
|
||||||
|
/// outside [`run`](crate::run).
|
||||||
pub fn send<M: Send + 'static>(name: Name<M>, msg: M) -> Result<(), SendError<M>> {
|
pub fn send<M: Send + 'static>(name: Name<M>, msg: M) -> Result<(), SendError<M>> {
|
||||||
let key = name.as_str();
|
let key = name.as_str();
|
||||||
with_runtime(|inner| {
|
with_runtime(|inner| {
|
||||||
// Resolve + clone the sender under the Leaf lock, then drop the lock
|
// Resolve + clone the sender under the registry lock, then drop the
|
||||||
// before sending (a send can unpark a receiver).
|
// lock before sending (a send can unpark a receiver).
|
||||||
let tx = {
|
let tx = {
|
||||||
let mut reg = inner.registry.lock();
|
let mut reg = inner.registry.lock();
|
||||||
let pid = match reg.by_name.get(key) {
|
let pid = match reg.by_name.get(key) {
|
||||||
@@ -489,11 +561,9 @@ pub fn send<M: Send + 'static>(name: Name<M>, msg: M) -> Result<(), SendError<M>
|
|||||||
None => return Err(SendError::Unresolved(msg)),
|
None => return Err(SendError::Unresolved(msg)),
|
||||||
};
|
};
|
||||||
if !live(inner, pid) {
|
if !live(inner, pid) {
|
||||||
// Stored-pid liveness (generation included). Previously this
|
// Stored-pid liveness (generation included), so a recycled
|
||||||
// checked the slot's *current* mailbox pid, so a recycled
|
// slot's new live tenant is never mistaken for the name's
|
||||||
// slot's live tenant passed — and a same-typed tenant would
|
// original (now-dead) holder.
|
||||||
// have received the message (misdelivery), a differently
|
|
||||||
// typed one a misleading NoChannel.
|
|
||||||
reg.prune_holder(pid);
|
reg.prune_holder(pid);
|
||||||
return Err(SendError::Unresolved(msg));
|
return Err(SendError::Unresolved(msg));
|
||||||
}
|
}
|
||||||
@@ -508,18 +578,19 @@ pub fn send<M: Send + 'static>(name: Name<M>, msg: M) -> Result<(), SendError<M>
|
|||||||
|
|
||||||
/// Resolve a *raw* pid to its mailbox and deliver `msg` on the channel for `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
|
/// with **no redirect**. The stored mailbox must be this exact incarnation
|
||||||
/// (generation included) and still live; otherwise the actor this pid named is
|
/// (generation included) and still live; otherwise the actor this pid named
|
||||||
/// gone and the result is [`SendError::Dead`] — even when the slot now holds a
|
/// is gone and the result is [`SendError::Dead`], even when the slot now
|
||||||
/// different, live actor (which we leave untouched). Shared by [`send_to`]
|
/// holds a different, live actor (which is left untouched). Shared by
|
||||||
/// (typed, `M = A::Msg`, channel guaranteed on an installed actor) and
|
/// [`send_to`] (typed, `M = A::Msg`, channel guaranteed on an installed
|
||||||
/// [`send_dyn`] (explicit `M`, where `NoChannel` is a real outcome).
|
/// actor) and [`send_dyn`] (explicit `M`, where `NoChannel` is a real
|
||||||
|
/// outcome).
|
||||||
fn send_to_pid<M: Send + 'static>(
|
fn send_to_pid<M: Send + 'static>(
|
||||||
inner: &crate::runtime::RuntimeInner,
|
inner: &crate::runtime::RuntimeInner,
|
||||||
pid: Pid,
|
pid: Pid,
|
||||||
msg: M,
|
msg: M,
|
||||||
) -> Result<(), SendError<M>> {
|
) -> Result<(), SendError<M>> {
|
||||||
// Resolve + clone the sender under the Leaf lock, then drop the lock before
|
// Resolve + clone the sender under the registry lock, then drop the lock
|
||||||
// sending (a send can unpark a receiver) — Leaf -> Channel, as name `send`.
|
// before sending (a send can unpark a receiver), same order as `send`.
|
||||||
let tx = {
|
let tx = {
|
||||||
let mut reg = inner.registry.lock();
|
let mut reg = inner.registry.lock();
|
||||||
match reg.by_index.get(&pid.index()).map(|m| m.pid) {
|
match reg.by_index.get(&pid.index()).map(|m| m.pid) {
|
||||||
@@ -543,32 +614,36 @@ fn send_to_pid<M: Send + 'static>(
|
|||||||
tx.send(msg).map_err(|crate::channel::SendError(m)| SendError::Closed(m))
|
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,
|
/// Deliver `msg` directly to the exact actor identified by `pid`. Unlike
|
||||||
/// identity-bound addressing mode. Unlike name-addressed [`send`] there is **no
|
/// name-addressed [`send`], there is **no redirect**: if that specific actor
|
||||||
/// redirect**: if that incarnation has died the message comes back as
|
/// has died, the message comes back as [`SendError::Dead`], even if its slot
|
||||||
/// [`SendError::Dead`], even if its slot now holds a different actor.
|
/// has since been taken over by a different, live actor. Use this when you
|
||||||
|
/// already hold a `Pid<A>` and want to talk to that one actor specifically;
|
||||||
|
/// use [`send`] with a [`Name`] when you want whichever actor currently holds
|
||||||
|
/// a name.
|
||||||
///
|
///
|
||||||
/// The message type is the actor's `A::Msg`, so on a live actor that has
|
/// 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
|
/// installed its inbox (via [`install`] or [`register`]) the channel is
|
||||||
/// present; [`SendError::NoChannel`] therefore means the actor is live but
|
/// always present; [`SendError::NoChannel`] therefore means the actor is live
|
||||||
/// never published a `Pid<A>`-reachable inbox. Panics if called outside
|
/// but never published a `Pid<A>`-reachable inbox. Panics if called outside
|
||||||
/// `Runtime::run()`.
|
/// [`run`](crate::run).
|
||||||
pub fn send_to<A: Addressable>(pid: Pid<A>, msg: A::Msg) -> Result<(), SendError<A::Msg>> {
|
pub fn send_to<A: Addressable>(pid: Pid<A>, msg: A::Msg) -> Result<(), SendError<A::Msg>> {
|
||||||
with_runtime(|inner| send_to_pid::<A::Msg>(inner, pid.erase(), msg))
|
with_runtime(|inner| send_to_pid::<A::Msg>(inner, pid.erase(), msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The explicit bare-pid escape hatch (RFC 014 §4.6): deliver `msg` of type `M`
|
/// The escape hatch for sending to a bare, untyped [`Pid`] when the typed
|
||||||
/// to `pid` when all you hold is an untyped [`Pid`] — a pid off a [`Down`], or
|
/// [`send_to`] is unavailable, for example a pid recovered from a [`Down`]
|
||||||
/// out of a future `members()` — so the typed [`send_to`] is unavailable.
|
/// notification or a group's `members()` list, where you no longer know the
|
||||||
|
/// actor's message type at compile time.
|
||||||
///
|
///
|
||||||
/// This is the one send whose message type can genuinely be wrong: the actor
|
/// Because the message type is not checked at compile time here, this is the
|
||||||
/// may be live yet expose no channel for `M`, returning [`SendError::NoChannel`]
|
/// one send that can genuinely be live-but-wrong: the actor may be alive yet
|
||||||
/// (on the typed paths that downcast collapses to a `debug_assert`). It is
|
/// expose no channel for `M`, in which case you get [`SendError::NoChannel`]
|
||||||
/// named and documented as the fallible fallback so the typed `Pid<A>` /
|
/// back instead of a misdelivery. Liveness and redirect behavior are
|
||||||
/// `Name<M>` paths stay the obvious default and an agentic caller reaches for a
|
/// otherwise identical to [`send_to`]: identity-bound, no redirect,
|
||||||
/// present primitive instead of inventing a workaround. Liveness is identical
|
/// [`SendError::Dead`] once the addressed incarnation is gone. Prefer
|
||||||
/// to [`send_to`]: identity-bound, no redirect, [`SendError::Dead`] once the
|
/// `send_to` with a typed `Pid<A>` whenever you have one; reach for this only
|
||||||
/// addressed incarnation is gone. Panics if called outside `Runtime::run()`.
|
/// when you don't. Panics if called outside [`run`](crate::run).
|
||||||
///
|
///
|
||||||
/// [`Down`]: crate::Down
|
/// [`Down`]: crate::Down
|
||||||
pub fn send_dyn<M: Send + 'static>(pid: Pid, msg: M) -> Result<(), SendError<M>> {
|
pub fn send_dyn<M: Send + 'static>(pid: Pid, msg: M) -> Result<(), SendError<M>> {
|
||||||
|
|||||||
Reference in New Issue
Block a user