//! 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, install, register, run, send, send_to, spawn, unregister, whereis, Addressable, Name, Pid, RegisterError, SendError, }; const SVC: Name = Name::new("svc"); #[test] fn register_then_send_by_name_delivers() { run(|| { let (ready_tx, ready_rx) = channel::<()>(); let (tx, rx) = channel::(); 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` 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::(); let (adm_tx, adm_rx) = channel::<&'static str>(); let h = spawn(move || { register(Name::::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::::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::(); 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::(); 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::(); 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::(); 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::::new("ghost"), 1u64), Err(SendError::Unresolved(_)))); let (ready_tx, ready_rx) = channel::<()>(); let (tx, rx) = channel::(); let h = spawn(move || { register(Name::::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::::new("svc2"), "x".to_string()); assert!(matches!(e, Err(SendError::NoChannel(_)))); assert_eq!(e.unwrap_err().into_inner(), "x"); // message handed back send(Name::::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::(); // _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(); }); } // --- RFC 014 §4.2: direct, identity-bound addressing via `Pid` ---------- // A stand-in single-message actor. `install::` publishes its inbox and // returns a `Pid` 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::>(); let h = spawn(move || { let (tx, rx) = channel::(); let me = install::(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 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` 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::>(); let (rt1, rr1) = channel::<()>(); let a = spawn(move || { let (tx, _rx) = channel::(); let me = install::(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::>(); let (rt2, rr2) = channel::<()>(); let b = spawn(move || { let (tx, rx) = channel::(); let me = install::(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(); }); }