Rip out the old name<->pid bimap; rebuild registry.rs as the live mailbox directory. A name resolves to a SINGLE actor (many-per-label is pg's job); that actor owns a SET of typed channels (channels are typed, so no single mailbox), keyed by message TypeId. Resolution: name -> pid (one actor) -> Mailbox -> channel for type M. Same name + different type parameter selects different channels of the one actor, so capability separation (§4.7) needs no new type. - register<M>(Name<M>, Sender<M>): captures the current actor's channel under a name (one step, per decision). Adds channels cumulatively; NameTaken only vs a different LIVE holder; stale/dangling bindings pruned on contact. - send<M>(Name<M>, msg): the payoff — resolve, clone the Sender UNDER the Leaf lock, release, then send (Leaf -> Channel; a send can unpark). Returns the msg on Unresolved / NoChannel / Closed. - whereis -> single live pid; unregister frees only the name (mailbox/other names survive). name_of (reverse lookup) dropped — deferred to introspection. - Contained erasure: each channel is Box<dyn Any+Send> filed under TypeId::of M and downcast to its own keying type, so the downcast can't fail on good data (debug-asserted via the stored type_name). Phantom M on Name re-imposes type. - One Leaf RawMutex (the fold), so a name send stays on a single Leaf — two Leaves at once is a hard panic. runtime.rs field/init unchanged (same Registry name + new()). tests/registry.rs rewritten for the new semantics: send-by-name delivery, many typed channels on one actor, same-name/different-type routing, NameTaken, takeover across slot reuse, Unresolved/NoChannel. Also fold in a Phase 1 fixup: the phantom test key was an enum whose variants tripped dead-code under the test build (only caught now that I run -D warnings on --tests). Full suite + order-checker green, warning-free across all targets.
145 lines
5.4 KiB
Rust
145 lines
5.4 KiB
Rust
//! Mailbox-registry tests (RFC 014). Run under the scheduler: registration
|
|
//! captures a live actor's channel, resolution checks liveness.
|
|
//!
|
|
//! Workers register their *own* inbox (`register` claims the current actor);
|
|
//! 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};
|
|
|
|
const SVC: Name<u64> = Name::new("svc");
|
|
|
|
#[test]
|
|
fn register_then_send_by_name_delivers() {
|
|
run(|| {
|
|
let (ready_tx, ready_rx) = channel::<()>();
|
|
let (tx, rx) = channel::<u64>();
|
|
let h = spawn(move || {
|
|
register(SVC, tx).unwrap();
|
|
ready_tx.send(()).unwrap();
|
|
assert_eq!(rx.recv().unwrap(), 42);
|
|
});
|
|
ready_rx.recv().unwrap(); // worker has registered
|
|
assert_eq!(whereis("svc"), Some(h.pid()));
|
|
send(SVC, 42).unwrap();
|
|
h.join().unwrap();
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn one_actor_many_typed_channels_route_by_type() {
|
|
// Same name, two message types: capability separation falls out of the
|
|
// type parameter — `Name<u64>` and `Name<&str>` hit different channels of
|
|
// the one actor (RFC 014 §4.7).
|
|
run(|| {
|
|
let (ready_tx, ready_rx) = channel::<()>();
|
|
let (cmd_tx, cmd_rx) = channel::<u64>();
|
|
let (adm_tx, adm_rx) = channel::<&'static str>();
|
|
let h = spawn(move || {
|
|
register(Name::<u64>::new("port"), cmd_tx).unwrap();
|
|
register(Name::<&'static str>::new("port"), adm_tx).unwrap();
|
|
ready_tx.send(()).unwrap();
|
|
assert_eq!(cmd_rx.recv().unwrap(), 7);
|
|
assert_eq!(adm_rx.recv().unwrap(), "halt");
|
|
});
|
|
ready_rx.recv().unwrap();
|
|
send(Name::<u64>::new("port"), 7u64).unwrap();
|
|
send(Name::<&'static str>::new("port"), "halt").unwrap();
|
|
h.join().unwrap();
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn name_held_by_live_actor_is_taken() {
|
|
run(|| {
|
|
let (ready_tx, ready_rx) = channel::<()>();
|
|
let (tx_a, rx_a) = channel::<u64>();
|
|
let a = spawn(move || {
|
|
register(SVC, tx_a).unwrap();
|
|
ready_tx.send(()).unwrap();
|
|
assert_eq!(rx_a.recv().unwrap(), 0); // wait to be released
|
|
});
|
|
ready_rx.recv().unwrap();
|
|
// Root tries to claim a live actor's name for itself -> NameTaken.
|
|
let (tx_b, _rx_b) = channel::<u64>();
|
|
assert_eq!(register(SVC, tx_b), Err(RegisterError::NameTaken { holder: a.pid() }));
|
|
send(SVC, 0).unwrap(); // release a (delivers to the holder, a)
|
|
a.join().unwrap();
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn dead_holder_is_pruned_and_name_taken_over() {
|
|
// a registers, signals, then dies. Its slot index is typically reused by b;
|
|
// b's registration must prune the stale binding and take the name over.
|
|
run(|| {
|
|
let (rt1, rr1) = channel::<()>();
|
|
let (tx1, _rx1) = channel::<u64>();
|
|
let a = spawn(move || {
|
|
register(SVC, tx1).unwrap();
|
|
rt1.send(()).unwrap(); // then return -> die, _rx1 dropped
|
|
});
|
|
rr1.recv().unwrap();
|
|
let a_pid = a.pid();
|
|
a.join().unwrap(); // a is dead; "svc" now points at a stale pid
|
|
|
|
let (rt2, rr2) = channel::<()>();
|
|
let (tx2, rx2) = channel::<u64>();
|
|
let b = spawn(move || {
|
|
register(SVC, tx2).unwrap(); // takes over the freed name
|
|
rt2.send(()).unwrap();
|
|
assert_eq!(rx2.recv().unwrap(), 9);
|
|
});
|
|
rr2.recv().unwrap();
|
|
assert_ne!(b.pid(), a_pid); // distinct incarnation even if slot reused
|
|
assert_eq!(whereis("svc"), Some(b.pid()));
|
|
send(SVC, 9).unwrap();
|
|
b.join().unwrap();
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn send_errors_unresolved_and_no_channel() {
|
|
run(|| {
|
|
// No actor at all.
|
|
assert!(matches!(send(Name::<u64>::new("ghost"), 1u64), Err(SendError::Unresolved(_))));
|
|
|
|
let (ready_tx, ready_rx) = channel::<()>();
|
|
let (tx, rx) = channel::<u64>();
|
|
let h = spawn(move || {
|
|
register(Name::<u64>::new("svc2"), tx).unwrap();
|
|
ready_tx.send(()).unwrap();
|
|
assert_eq!(rx.recv().unwrap(), 0);
|
|
});
|
|
ready_rx.recv().unwrap();
|
|
// Right actor, wrong message type: it has a u64 channel, not a String.
|
|
let e = send(Name::<String>::new("svc2"), "x".to_string());
|
|
assert!(matches!(e, Err(SendError::NoChannel(_))));
|
|
assert_eq!(e.unwrap_err().into_inner(), "x"); // message handed back
|
|
send(Name::<u64>::new("svc2"), 0u64).unwrap();
|
|
h.join().unwrap();
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn unregister_frees_the_name_only() {
|
|
run(|| {
|
|
let (ready_tx, ready_rx) = channel::<()>();
|
|
let (done_tx, done_rx) = channel::<()>();
|
|
let (tx, _rx) = channel::<u64>(); // _rx moves into the actor, kept open
|
|
let h = spawn(move || {
|
|
register(SVC, tx).unwrap();
|
|
let _keep_open = _rx;
|
|
ready_tx.send(()).unwrap();
|
|
done_rx.recv().unwrap(); // released over a separate channel
|
|
});
|
|
ready_rx.recv().unwrap();
|
|
assert_eq!(whereis("svc"), Some(h.pid()));
|
|
assert_eq!(unregister("svc"), Some(h.pid()));
|
|
assert_eq!(whereis("svc"), None);
|
|
assert!(matches!(send(SVC, 1u64), Err(SendError::Unresolved(_))));
|
|
done_tx.send(()).unwrap();
|
|
h.join().unwrap();
|
|
});
|
|
}
|