mailbox: Phase 4a — direct identity-bound send to Pid<A> (RFC 014 §4.2)
Make a Pid<A> messageable: install + send_to, the direct addressing mode that dies with the actor and never redirects (the counterpart to the re-resolving Name). - install::<A: Addressable>(tx: Sender<A::Msg>) -> Pid<A>: opt-in, lazy, nameless publish (RFC 014 §5). Files the actor's inbox into the by_index mailbox table under TypeId::of::<A::Msg>() and re-types self's identity as Pid<A>. Infallible: no name to collide on, self is always live in run(). - register and install now share publish_channel (the insert-or-extend-mailbox step factored out); register additionally binds the name. Byte-identical storage, so name- and pid-addressing populate the same table. - send_to::<A>(Pid<A>, A::Msg): resolve by raw pid, deliver with NO redirect. The stored mailbox must be this exact incarnation (generation included) and live, else SendError::Dead — even when the slot now holds a different live actor, which is left untouched. Resolution clones the Sender under the Leaf lock, releases, then sends (Leaf -> Channel), as name send / pg / finalize. - send_to_pid is the shared raw-pid core; send_dyn (§4.6) lands on it next. - SendError gains Dead(M), the pid-path counterpart to name-path Unresolved; into_inner / Display / Debug updated. Name send never yields Dead, pid send never yields Unresolved — disjoint by construction, documented on the enum. tests/registry.rs: install_then_send_to_pid_delivers; and the load-bearing send_to_does_not_redirect_after_takeover (slot reused by a new incarnation, the stale Pid<A> send returns Dead and the new occupant gets only its own message). Full suite + order-checker green, warning-free across all targets.
This commit is contained in:
+68
-1
@@ -5,7 +5,10 @@
|
||||
//! the root closure resolves and sends by name. A `ready` handshake closes the
|
||||
//! register-then-send race without busy-waiting on `whereis`.
|
||||
|
||||
use smarm::{channel, register, run, send, spawn, unregister, whereis, Name, RegisterError, SendError};
|
||||
use smarm::{
|
||||
channel, install, register, run, send, send_to, spawn, unregister, whereis, Addressable, Name,
|
||||
Pid, RegisterError, SendError,
|
||||
};
|
||||
|
||||
const SVC: Name<u64> = Name::new("svc");
|
||||
|
||||
@@ -142,3 +145,67 @@ fn unregister_frees_the_name_only() {
|
||||
h.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
// --- RFC 014 §4.2: direct, identity-bound addressing via `Pid<A>` ----------
|
||||
|
||||
// A stand-in single-message actor. `install::<Worker>` publishes its inbox and
|
||||
// returns a `Pid<Worker>` that delivers `u64` to exactly that incarnation.
|
||||
struct Worker;
|
||||
impl Addressable for Worker {
|
||||
type Msg = u64;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn install_then_send_to_pid_delivers() {
|
||||
run(|| {
|
||||
let (addr_tx, addr_rx) = channel::<Pid<Worker>>();
|
||||
let h = spawn(move || {
|
||||
let (tx, rx) = channel::<u64>();
|
||||
let me = install::<Worker>(tx); // nameless publish, typed pid back
|
||||
addr_tx.send(me).unwrap();
|
||||
assert_eq!(rx.recv().unwrap(), 42);
|
||||
});
|
||||
let addr = addr_rx.recv().unwrap(); // worker installed, handed its Pid<Worker>
|
||||
assert_eq!(addr.erase(), h.pid()); // same identity, just re-typed
|
||||
send_to(addr, 42u64).unwrap();
|
||||
h.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_to_does_not_redirect_after_takeover() {
|
||||
// The load-bearing §4.2 property: a `Pid<A>` is identity-bound. When the
|
||||
// actor dies and a new incarnation reuses the slot, sending to the *old*
|
||||
// address fails `Dead` — it must never silently reach the new occupant
|
||||
// (that redirect is the re-resolving `Name`'s job, not a pid's).
|
||||
run(|| {
|
||||
let (addr_a_tx, addr_a_rx) = channel::<Pid<Worker>>();
|
||||
let (rt1, rr1) = channel::<()>();
|
||||
let a = spawn(move || {
|
||||
let (tx, _rx) = channel::<u64>();
|
||||
let me = install::<Worker>(tx);
|
||||
addr_a_tx.send(me).unwrap();
|
||||
rt1.send(()).unwrap(); // then return -> die
|
||||
});
|
||||
let a_addr = addr_a_rx.recv().unwrap();
|
||||
rr1.recv().unwrap();
|
||||
a.join().unwrap(); // a dead; a_addr names a dead incarnation
|
||||
|
||||
let (addr_b_tx, addr_b_rx) = channel::<Pid<Worker>>();
|
||||
let (rt2, rr2) = channel::<()>();
|
||||
let b = spawn(move || {
|
||||
let (tx, rx) = channel::<u64>();
|
||||
let me = install::<Worker>(tx); // reuses a's slot index, new generation
|
||||
addr_b_tx.send(me).unwrap();
|
||||
rt2.send(()).unwrap();
|
||||
// Only b's own message ever arrives; the stale send never redirects.
|
||||
assert_eq!(rx.recv().unwrap(), 7);
|
||||
});
|
||||
let b_addr = addr_b_rx.recv().unwrap();
|
||||
rr2.recv().unwrap();
|
||||
assert_ne!(b_addr.erase(), a_addr.erase()); // distinct incarnation
|
||||
assert!(matches!(send_to(a_addr, 99u64), Err(SendError::Dead(_)))); // no redirect
|
||||
send_to(b_addr, 7u64).unwrap(); // b's real message
|
||||
b.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user