feat(link): bidirectional links + trap_exit (roadmap #3)

Add Erlang-style process links so an abnormal death fate-shares across a
link set, with trap_exit to convert that into a message instead.

- Slot.links: Vec<Pid>, bidirectional; reset in all three lifecycle sites.
- Actor.trap: Option<Sender<ExitSignal>>, fresh per spawn (a restarted
  child starts un-trapped; no fourth reset site).
- link/unlink free functions on self_pid(); trap_exit() -> Receiver<ExitSignal>
  (a dedicated inbox, distinct from the monitor Down channel).
- finalize_actor clears reverse links under the lock (always; keeps the
  cascade acyclic), then propagates abnormal deaths post-lock: trapping peer
  gets an ExitSignal message and survives, non-trapping peer is request_stop'd.
  Normal exit never propagates. Dead-pid link delivers an immediate NoProc
  (message if trapping, else request_stop(self) -- never a silent no-op).
- ExitSignal reuses DownReason and carries no panic payload (joiner-only).

Tests in tests/link.rs cover the propagate/trap/normal/dead-pid/unlink cases.
This commit is contained in:
smarm-dev
2026-06-07 21:51:56 +00:00
parent 59998ce79e
commit 8ac11a57ac
6 changed files with 443 additions and 2 deletions
+2
View File
@@ -157,6 +157,7 @@ pub fn spawn_under(supervisor: Pid, f: impl FnOnce() + Send + 'static) -> JoinHa
sp,
supervisor,
stop: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
trap: None,
});
slot.state = crate::runtime::State::Runnable;
slot.outstanding_handles = 1;
@@ -164,6 +165,7 @@ pub fn spawn_under(supervisor: Pid, f: impl FnOnce() + Send + 'static) -> JoinHa
slot.waiters.clear();
slot.supervisor_channel = None;
slot.monitors.clear();
slot.links.clear();
slot.pending_unpark = false;
slot.pending_io_result = None;
s.run_queue.push_back(pid);