mailbox: typed-path producers, by-name gen_servers, root-exit teardown (RFC 014)
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.
This commit is contained in:
+81
-5
@@ -96,8 +96,13 @@ pub enum SendError<M> {
|
||||
Dead(M),
|
||||
/// The actor is live but exposes no channel for this message type.
|
||||
NoChannel(M),
|
||||
/// The actor's channel for this type is closed (its receiver is gone).
|
||||
/// 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> {
|
||||
@@ -107,7 +112,8 @@ impl<M> SendError<M> {
|
||||
SendError::Unresolved(m)
|
||||
| SendError::Dead(m)
|
||||
| SendError::NoChannel(m)
|
||||
| SendError::Closed(m) => m,
|
||||
| SendError::Closed(m)
|
||||
| SendError::NoMember(m) => m,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +123,7 @@ impl<M> SendError<M> {
|
||||
SendError::Dead(_) => "Dead",
|
||||
SendError::NoChannel(_) => "NoChannel",
|
||||
SendError::Closed(_) => "Closed",
|
||||
SendError::NoMember(_) => "NoMember",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,6 +141,7 @@ impl<M> std::fmt::Display for SendError<M> {
|
||||
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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,8 +220,20 @@ fn live(inner: &crate::runtime::RuntimeInner, pid: Pid) -> bool {
|
||||
/// 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> {
|
||||
let me = self_pid();
|
||||
let key = name.as_str();
|
||||
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) {
|
||||
@@ -231,7 +251,7 @@ pub fn register<M: Send + 'static>(name: Name<M>, tx: Sender<M>) -> Result<(), R
|
||||
}
|
||||
}
|
||||
}
|
||||
// Publish (or extend) my mailbox with this channel, then bind the name.
|
||||
// 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(())
|
||||
@@ -276,6 +296,23 @@ pub fn install<A: Addressable>(tx: Sender<A::Msg>) -> Pid<A> {
|
||||
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> {
|
||||
@@ -296,6 +333,45 @@ pub fn whereis(name: &str) -> Option<Pid> {
|
||||
})
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
|
||||
Reference in New Issue
Block a user