RFC 016 Chunk 3: parentage tree view
tree() / tree_from() fold a Chunk-1 snapshot into a forest by grouping each actor under its parent pid — one O(n) pass, no new reads. tree_from is public so a held (or synthetic) snapshot can be folded without a second scan. - D8: actors parented at ROOT_PID are genuine roots; an actor whose recorded parent is absent from the snapshot is re-rooted under the sentinel and flagged orphaned, so the forest stays total. take()-on- place doubles as a guard against re-entering a node. - D9: the edge is parent/spawned-by, documented as not necessarily supervision.
This commit is contained in:
+78
-2
@@ -4,8 +4,8 @@
|
||||
//! instead of sleeping-and-hoping.
|
||||
|
||||
use smarm::{
|
||||
actor_info, channel, monitor, register, run, self_pid, send, snapshot, spawn, ActorState, Name,
|
||||
Pid, SNAPSHOT_FORMAT_VERSION,
|
||||
actor_info, channel, monitor, register, run, self_pid, send, snapshot, spawn, tree, tree_from,
|
||||
ActorInfo, ActorState, Name, Pid, RuntimeSnapshot, SNAPSHOT_FORMAT_VERSION,
|
||||
};
|
||||
|
||||
const SVC: Name<u64> = Name::new("svc");
|
||||
@@ -182,3 +182,79 @@ fn done_actor_is_a_tombstone() {
|
||||
h.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_places_child_under_its_spawner() {
|
||||
run(|| {
|
||||
let (ready_tx, ready_rx) = channel::<()>();
|
||||
let (gate_tx, gate_rx) = channel::<()>();
|
||||
let h = spawn(move || {
|
||||
ready_tx.send(()).unwrap();
|
||||
gate_rx.recv().unwrap();
|
||||
});
|
||||
ready_rx.recv().unwrap();
|
||||
|
||||
let me = self_pid();
|
||||
let t = tree();
|
||||
assert_eq!(t.format_version, SNAPSHOT_FORMAT_VERSION);
|
||||
|
||||
// The root is parented at the forest sentinel, so it's a genuine root,
|
||||
// and the worker it spawned hangs beneath it.
|
||||
let root = t.roots.iter().find(|n| n.info.pid == me).expect("root in forest");
|
||||
assert!(!root.orphaned);
|
||||
assert!(
|
||||
root.children.iter().any(|c| c.info.pid == h.pid()),
|
||||
"spawned worker should be a child of its spawner"
|
||||
);
|
||||
|
||||
gate_tx.send(()).unwrap();
|
||||
h.join().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
/// D8 re-rooting and nesting, exercised on a synthetic snapshot via the public
|
||||
/// `tree_from` — the live lifecycle race (parent reclaimed while child lives)
|
||||
/// is exactly what's awkward to stage deterministically, which is why the fold
|
||||
/// is testable in isolation.
|
||||
#[test]
|
||||
fn tree_from_nests_children_and_reroots_orphans() {
|
||||
let root_sentinel = Pid::new(u32::MAX, u32::MAX);
|
||||
let root_pid = Pid::new(0, 1);
|
||||
let child = Pid::new(1, 1);
|
||||
let orphan = Pid::new(2, 1);
|
||||
let absent_parent = Pid::new(99, 1);
|
||||
|
||||
let mk = |pid: Pid, supervisor: Pid| ActorInfo {
|
||||
pid,
|
||||
names: Vec::new(),
|
||||
state: ActorState::Running,
|
||||
supervisor,
|
||||
trap_exit: false,
|
||||
monitors: 0,
|
||||
links: 0,
|
||||
joiners: 0,
|
||||
mailbox_depth: 0,
|
||||
};
|
||||
|
||||
let snap = RuntimeSnapshot {
|
||||
format_version: SNAPSHOT_FORMAT_VERSION,
|
||||
actors: vec![
|
||||
mk(root_pid, root_sentinel),
|
||||
mk(child, root_pid),
|
||||
mk(orphan, absent_parent),
|
||||
],
|
||||
};
|
||||
|
||||
let t = tree_from(snap);
|
||||
assert_eq!(t.roots.len(), 2);
|
||||
|
||||
let root = t.roots.iter().find(|n| n.info.pid == root_pid).expect("root present");
|
||||
assert!(!root.orphaned);
|
||||
assert_eq!(root.children.len(), 1);
|
||||
assert_eq!(root.children[0].info.pid, child);
|
||||
assert!(!root.children[0].orphaned);
|
||||
|
||||
let o = t.roots.iter().find(|n| n.info.pid == orphan).expect("orphan re-rooted");
|
||||
assert!(o.orphaned, "an actor whose parent is absent must be flagged orphaned");
|
||||
assert!(o.children.is_empty());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user