docs: mark links/trap_exit (#3) done; refresh supervisor + README

- task.md: mark roadmap #3 done (record the resolved dedicated-inbox
  decision + test list), add the feature commit to the resume block, note
  the now-resolved trap_exit channel question, and list spawn_link under #5.
- supervisor.rs: the module comment predated cancellation; rewrite it to say
  sibling/link/shutdown stops are cooperative (request_stop), not that
  one_for_all / rest_for_one / links are impossible.
- README: add monitor + link rows, refresh the supervisor row and src layout,
  and drop restart-intensity caps from "what's not here" (shipped in #2).
This commit is contained in:
smarm-dev
2026-06-07 21:51:56 +00:00
parent 8ac11a57ac
commit 70bd237277
3 changed files with 44 additions and 26 deletions
+28 -16
View File
@@ -5,7 +5,7 @@ before starting; the gotchas section is hard-won and will save you a faceplant.
## Resume the environment
- Repo: `/home/claude/work/smarm`, branch `arm-port`, currently 6 commits past
- Repo: `/home/claude/work/smarm`, branch `arm-port`, currently 8 commits past
`master`:
- `a8fec02` aarch64 context-switch port
- `183fd05` monitors (`monitor()``Receiver<Down>`)
@@ -13,6 +13,9 @@ before starting; the gotchas section is hard-won and will save you a faceplant.
- `a8ddb4a` cooperative cancellation (roadmap #1 — done)
- `e80334b` fix: orphaned timers no longer block runtime shutdown
- `351dc9c` one_for_all / rest_for_one + ordered shutdown (roadmap #2 — done)
- `31133a6` docs(roadmap): mark #1 and #2 done
- `6581484` links + trap_exit (roadmap #3 — done)
- (+ a trailing docs commit marking #3 done and refreshing supervisor/README)
- Toolchain is installed but NOT on PATH in a fresh shell. First line of every
session: `. "$HOME/.cargo/env"` (rustc/cargo 1.96).
- Build `cargo build` · all tests `cargo test` · one suite `cargo test --test monitor`.
@@ -101,19 +104,24 @@ Shipped. What landed vs the plan:
rather than folding into Exit (clearer for the supervisor's await logic).
- Tests: all-restart, suffix-restart, reverse-order shutdown.
### 3. Links + trap_exit ← NEXT
- `Slot.links: Vec<Pid>` (bidirectional); `link`/`unlink`; per-actor
`trap_exit` flag.
- On finalize, for each linked peer: abnormal death → `request_stop(peer)`
unless peer traps, in which case deliver an exit *message* instead. Normal
exit does NOT propagate. Linking an already-dead pid delivers an immediate
exit signal.
- Open decision: which channel a trapping actor reads exit messages from
(reuse a Down-style channel vs a dedicated trap inbox) — ties into the
no-unified-mailbox constraint below. (`Signal::Stopped` + the reverse-order
stop helper from #2 are now available to build on.)
- Tests: linked pair one panics → other stopped; with trap_exit → other gets a
message and survives; normal exit doesn't propagate.
### 3. Links + trap_exit ✅ DONE (`6581484`)
- `Slot.links: Vec<Pid>` (bidirectional); `link`/`unlink`; `trap_exit()` flag
lives on `Actor` (fresh per spawn → a restarted child starts un-trapped, and
no fourth slot-reset site).
- On finalize, reverse links are cleared under the lock (always — keeps the
cascade acyclic), then for each linked peer: abnormal death (`Panic`/
`Stopped`) → `request_stop(peer)` unless peer traps, in which case deliver an
`ExitSignal` *message* instead. Normal exit does NOT propagate. Linking an
already-dead pid delivers an immediate `NoProc` signal (message if trapping,
else `request_stop(self)` — not a silent no-op).
- Resolved: the trap inbox is a **dedicated** channel (`trap_exit() ->
Receiver<ExitSignal>`), distinct from the monitor `Down` channel; `ExitSignal`
reuses `DownReason` and carries no panic payload (joiner-only, as with
monitors). `spawn_link` deferred to #5.
- Tests (`tests/link.rs`): linked pair one panics → other stopped (+Drop ran);
trap_exit → other gets a message and survives; normal exit doesn't propagate;
dead-pid link stops a non-trapper / messages a trapper; `unlink` prevents
propagation.
### 4. Selective receive (independent track)
Channels are strict FIFO MPSC; there's no pattern-matched `receive`.
@@ -126,6 +134,9 @@ Channels are strict FIFO MPSC; there's no pattern-matched `receive`.
remain for a later `recv`.
### 5. Grab-bag (each its own small commit)
- `spawn_link`: spawn-and-link atomically (deferred from #3); thin wrapper over
`spawn_under` + `link`, but do it under one lock so there's no window where
the child dies before the link is recorded.
- `demonitor`: needs a per-monitor id to remove a specific sender. Decide the
monitor API NOW before more code depends on it — likely return a
`Monitor { id, rx }` instead of a bare `Receiver<Down>`.
@@ -153,8 +164,9 @@ Channels are strict FIFO MPSC; there's no pattern-matched `receive`.
- **Pid = (index, generation);** stale handles caught by generation mismatch in
`slot()/slot_mut()`. The monitor `NoProc` path relies on this.
- **No `select`, no unified per-process mailbox.** Why the supervisor uses the
single `supervisor_channel` funnel rather than N monitor channels, and why
trap_exit/selective-receive delivery need an explicit channel decision.
single `supervisor_channel` funnel rather than N monitor channels. trap_exit
resolved this by giving each trapping actor a dedicated `Receiver<ExitSignal>`
inbox (see #3); selective-receive delivery still needs its own decision.
- **Cooperative-only**: preemption and (future) cancellation both depend on the
actor reaching `check!()`/yield/alloc/blocking points.
- `run()` is single-thread (`Config::exact(1)`); tests rely on deterministic