Implements the new addressing surface the examples in examples/ specify: - spawn_addr::<A>(FnOnce(Receiver<A::Msg>)) -> Pid<A>: the typed-path producer. Makes the inbox, spawns the body with its receiver, and publishes the sender from the PARENT side (registry::install_for) before returning the pid, so an immediate send_to always resolves (no race on the body installing itself). Detached, like ServerBuilder::start. - lookup_as / pick_as / members_as: re-type an erased pid as Pid<A> over the heterogeneous registry/pg stores, sharing one helper (pid::assert_type). Unchecked but sound: routing is by message TypeId, so a wrong A degrades to SendError::NoChannel on the next send, never a misdelivery. - dispatch::<A>(group, msg) -> Result<Pid<A>, SendError<A::Msg>>: pick_as + send_to, with the message handed back on failure. New SendError::NoMember variant for the empty/all-dead group case. - gen_server naming: ServerName<G> backed internally by the registry's existing typed-channel store keyed by TypeId::of::<Envelope<G>>() — Envelope stays private, no separate directory. Type-state NamedServerBuilder<G> keeps the current infallible ServerBuilder::start() untouched; its start() is fallible (parent-side register, NameTaken). Free call/cast/whereis_server resolve per use. ServerRef::shutdown (+ free shutdown) is the sys-style synchronous stop. - root-exit teardown: the run's initial actor is recorded as root; when it finalizes it flags root_exited, and the scheduler's idle verdict then stops the parked-forever remainder (a one-shot RootDrain sweep). Deferred to the idle point on purpose: the run queue drains first, so actors with queued work finish rather than unwinding on the stop. This lets a named-server daemon (or any pinned actor) wind the run down instead of hanging on live_actors > 0. request_stop is refactored to a request_stop_inner core so the sweep can drive it from inside the runtime without re-borrowing the thread-local. Lib + tests + examples build warning-free; full suite green.
495 lines
22 KiB
Rust
495 lines
22 KiB
Rust
//! 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<Cmd>` and a `Name<Admin>` 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<dyn Any + Send>` that is concretely a
|
|
//! `Sender<M>`, filed under `TypeId::of::<M>()`. A resolve for `M` looks up
|
|
//! that exact `TypeId` and downcasts to `Sender<M>` — 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<M> {
|
|
/// 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<A>` 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<M> SendError<M> {
|
|
/// 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<M> std::fmt::Debug for SendError<M> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "SendError::{}", self.variant())
|
|
}
|
|
}
|
|
|
|
impl<M> std::fmt::Display for SendError<M> {
|
|
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<M> std::error::Error for SendError<M> {}
|
|
|
|
/// 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
|
|
/// observers (RFC 014 §4.5) and as the debug cross-check on the downcast.
|
|
struct Channel {
|
|
sender: Box<dyn Any + Send>,
|
|
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<TypeId, Channel>,
|
|
}
|
|
|
|
impl Mailbox {
|
|
fn new(pid: Pid) -> Self {
|
|
Self { pid, channels: HashMap::new() }
|
|
}
|
|
|
|
/// Clone the `Sender<M>` 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<M: Send + 'static>(&self) -> Option<Sender<M>> {
|
|
let ch = self.channels.get(&TypeId::of::<M>())?;
|
|
let tx = ch
|
|
.sender
|
|
.downcast_ref::<Sender<M>>()
|
|
.expect("channel keyed by TypeId but downcast to its own type failed — smarm bug");
|
|
debug_assert_eq!(ch.msg_type, type_name::<M>(), "msg_type / TypeId disagree");
|
|
Some(tx.clone())
|
|
}
|
|
}
|
|
|
|
/// 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<u32, Mailbox>,
|
|
/// `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);
|
|
}
|
|
}
|
|
|
|
/// 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<M>` 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<M: Send + 'static>(name: Name<M>, tx: Sender<M>) -> 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<M: Send + 'static>(
|
|
me: Pid,
|
|
key: &'static str,
|
|
tx: Sender<M>,
|
|
) -> 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::<M>(&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<M: Send + 'static>(reg: &mut Registry, me: Pid, tx: Sender<M>) {
|
|
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::<M>(),
|
|
Channel { sender: Box::new(tx), msg_type: type_name::<M>() },
|
|
);
|
|
}
|
|
|
|
/// 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
|
|
/// 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<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
|
|
/// live actor inside `run()`, so this is infallible. Panics if called outside
|
|
/// `Runtime::run()`.
|
|
pub fn install<A: Addressable>(tx: Sender<A::Msg>) -> Pid<A> {
|
|
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::<A::Msg>(&mut reg, me, tx);
|
|
});
|
|
// `me` is this actor; re-type the identity as `Pid<A>` (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<A>`, 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<M: Send + 'static>(pid: Pid, tx: Sender<M>) {
|
|
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::<M>(&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<Pid> {
|
|
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<A>`] — the identity-bound counterpart of
|
|
/// [`whereis`] (RFC 014 §4.4). Recovers the compile-checked
|
|
/// [`send_to`](crate::send_to) path from a durable name: looks the name up,
|
|
/// then re-types the erased pid as `Pid<A>` 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<A: Addressable>(name: &str) -> Option<Pid<A>> {
|
|
whereis(name).map(crate::pid::assert_type::<A>)
|
|
}
|
|
|
|
/// Resolve `name` to its actor's pid and a cloned `Sender<M>`, 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<Envelope<G>>` (via [`register_with`]), and `whereis_server` / `call`
|
|
/// / `cast` recover that exact typed sender here to rebuild a `ServerRef<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>)> {
|
|
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::<M>)?;
|
|
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<Pid> {
|
|
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<M>` 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<M: Send + 'static>(name: Name<M>, msg: M) -> Result<(), SendError<M>> {
|
|
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::<M>) {
|
|
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<M: Send + 'static>(
|
|
inner: &crate::runtime::RuntimeInner,
|
|
pid: Pid,
|
|
msg: M,
|
|
) -> Result<(), SendError<M>> {
|
|
// 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::<M>) {
|
|
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<A>`-reachable inbox. Panics if called outside
|
|
/// `Runtime::run()`.
|
|
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))
|
|
}
|
|
|
|
/// 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<A>` /
|
|
/// `Name<M>` 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<M: Send + 'static>(pid: Pid, msg: M) -> Result<(), SendError<M>> {
|
|
with_runtime(|inner| send_to_pid::<M>(inner, pid, msg))
|
|
}
|