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:
@@ -25,7 +25,9 @@ convenience wrapper around `runtime::init(Config::exact(1)).run(f)`.
|
|||||||
| `mutex` | `Mutex<T>` with mandatory timeout; FIFO waiters; parks the green thread |
|
| `mutex` | `Mutex<T>` with mandatory timeout; FIFO waiters; parks the green thread |
|
||||||
| `timer` | Min-heap of `(deadline, reason)`; `Sleep` and `WaitTimeout` reasons |
|
| `timer` | Min-heap of `(deadline, reason)`; `Sleep` and `WaitTimeout` reasons |
|
||||||
| `io` | `block_on_io` for blocking work; `wait_readable`/`wait_writable` + `read`/`write` via epoll |
|
| `io` | `block_on_io` for blocking work; `wait_readable`/`wait_writable` + `read`/`write` via epoll |
|
||||||
| `supervisor` | `Signal::Exit` / `Signal::Panic` delivered to a parent actor's mailbox |
|
| `supervisor` | `Signal::Exit`/`Panic`/`Stopped` funnelled to a parent; `OneForOne`/`OneForAll`/`RestForOne` strategies + restart-intensity cap |
|
||||||
|
| `monitor` | `monitor(pid)` → one-shot `Receiver<Down>`; unidirectional death notice |
|
||||||
|
| `link` | bidirectional `link`/`unlink`; abnormal death propagates (cooperative stop, or an `ExitSignal` message under `trap_exit`) |
|
||||||
|
|
||||||
## Quick taste
|
## Quick taste
|
||||||
|
|
||||||
@@ -52,7 +54,8 @@ run(|| {
|
|||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
stack.rs context.rs preempt.rs pid.rs actor.rs
|
stack.rs context.rs preempt.rs pid.rs actor.rs
|
||||||
scheduler.rs channel.rs mutex.rs timer.rs io.rs supervisor.rs
|
scheduler.rs channel.rs mutex.rs timer.rs io.rs
|
||||||
|
supervisor.rs monitor.rs link.rs runtime.rs
|
||||||
lib.rs
|
lib.rs
|
||||||
tests/
|
tests/
|
||||||
per-module integration tests
|
per-module integration tests
|
||||||
@@ -76,7 +79,7 @@ cargo bench # primes benchmark vs tokio
|
|||||||
## What's not here
|
## What's not here
|
||||||
|
|
||||||
See the **Defer** section of `Architecture.md`.
|
See the **Defer** section of `Architecture.md`.
|
||||||
restart-intensity caps, `join!` for handle groups, stack growth via remap,
|
`join!` for handle groups, stack growth via remap,
|
||||||
hierarchical timer wheel, fd-wait timeouts, `Signal::Timeout`. Each is
|
hierarchical timer wheel, fd-wait timeouts, `Signal::Timeout`. Each is
|
||||||
mechanism we know how to add; none belongs in this iteration.
|
mechanism we know how to add; none belongs in this iteration.
|
||||||
|
|
||||||
|
|||||||
+10
-7
@@ -51,13 +51,16 @@ impl Signal {
|
|||||||
// whether to restart, and enforce a restart-intensity cap so a child that
|
// whether to restart, and enforce a restart-intensity cap so a child that
|
||||||
// crashes in a tight loop eventually gives up instead of spinning forever.
|
// crashes in a tight loop eventually gives up instead of spinning forever.
|
||||||
//
|
//
|
||||||
// What this deliberately does NOT do: forcibly terminate a running child.
|
// What this does NOT do: *forcibly* terminate a running child. smarm actors
|
||||||
// smarm actors share a heap and rely on Drop/RAII, so async teardown of a
|
// share a heap and rely on Drop/RAII, so tearing down a peer's stack from
|
||||||
// peer's stack from outside is unsound. That rules out `one_for_all` /
|
// outside is unsound. Stopping a sibling — whether for `one_for_all` /
|
||||||
// `rest_for_one` (which must stop siblings) and true bidirectional links
|
// `rest_for_one`, for a propagated link death, or for the ordered shutdown
|
||||||
// until there is a cooperative-cancellation primitive. When the intensity cap
|
// below — is therefore *cooperative*: `request_stop` flags the child and it
|
||||||
// trips, this supervisor simply stops restarting and returns; it does not kill
|
// unwinds at its next observation point. A child with no observation points
|
||||||
// whatever children are still alive.
|
// (a tight loop with no `check!()`, allocation, or blocking op) cannot be
|
||||||
|
// stopped, exactly as it cannot be preempted. When the intensity cap trips,
|
||||||
|
// this supervisor stops restarting and tears the remaining children down in
|
||||||
|
// reverse start order before returning.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
use crate::channel::channel;
|
use crate::channel::channel;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ before starting; the gotchas section is hard-won and will save you a faceplant.
|
|||||||
|
|
||||||
## Resume the environment
|
## 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`:
|
`master`:
|
||||||
- `a8fec02` aarch64 context-switch port
|
- `a8fec02` aarch64 context-switch port
|
||||||
- `183fd05` monitors (`monitor()` → `Receiver<Down>`)
|
- `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)
|
- `a8ddb4a` cooperative cancellation (roadmap #1 — done)
|
||||||
- `e80334b` fix: orphaned timers no longer block runtime shutdown
|
- `e80334b` fix: orphaned timers no longer block runtime shutdown
|
||||||
- `351dc9c` one_for_all / rest_for_one + ordered shutdown (roadmap #2 — done)
|
- `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
|
- Toolchain is installed but NOT on PATH in a fresh shell. First line of every
|
||||||
session: `. "$HOME/.cargo/env"` (rustc/cargo 1.96).
|
session: `. "$HOME/.cargo/env"` (rustc/cargo 1.96).
|
||||||
- Build `cargo build` · all tests `cargo test` · one suite `cargo test --test monitor`.
|
- 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).
|
rather than folding into Exit (clearer for the supervisor's await logic).
|
||||||
- Tests: all-restart, suffix-restart, reverse-order shutdown.
|
- Tests: all-restart, suffix-restart, reverse-order shutdown.
|
||||||
|
|
||||||
### 3. Links + trap_exit ← NEXT
|
### 3. Links + trap_exit ✅ DONE (`6581484`)
|
||||||
- `Slot.links: Vec<Pid>` (bidirectional); `link`/`unlink`; per-actor
|
- `Slot.links: Vec<Pid>` (bidirectional); `link`/`unlink`; `trap_exit()` flag
|
||||||
`trap_exit` flag.
|
lives on `Actor` (fresh per spawn → a restarted child starts un-trapped, and
|
||||||
- On finalize, for each linked peer: abnormal death → `request_stop(peer)`
|
no fourth slot-reset site).
|
||||||
unless peer traps, in which case deliver an exit *message* instead. Normal
|
- On finalize, reverse links are cleared under the lock (always — keeps the
|
||||||
exit does NOT propagate. Linking an already-dead pid delivers an immediate
|
cascade acyclic), then for each linked peer: abnormal death (`Panic`/
|
||||||
exit signal.
|
`Stopped`) → `request_stop(peer)` unless peer traps, in which case deliver an
|
||||||
- Open decision: which channel a trapping actor reads exit messages from
|
`ExitSignal` *message* instead. Normal exit does NOT propagate. Linking an
|
||||||
(reuse a Down-style channel vs a dedicated trap inbox) — ties into the
|
already-dead pid delivers an immediate `NoProc` signal (message if trapping,
|
||||||
no-unified-mailbox constraint below. (`Signal::Stopped` + the reverse-order
|
else `request_stop(self)` — not a silent no-op).
|
||||||
stop helper from #2 are now available to build on.)
|
- Resolved: the trap inbox is a **dedicated** channel (`trap_exit() ->
|
||||||
- Tests: linked pair one panics → other stopped; with trap_exit → other gets a
|
Receiver<ExitSignal>`), distinct from the monitor `Down` channel; `ExitSignal`
|
||||||
message and survives; normal exit doesn't propagate.
|
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)
|
### 4. Selective receive (independent track)
|
||||||
Channels are strict FIFO MPSC; there's no pattern-matched `receive`.
|
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`.
|
remain for a later `recv`.
|
||||||
|
|
||||||
### 5. Grab-bag (each its own small commit)
|
### 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
|
- `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 API NOW before more code depends on it — likely return a
|
||||||
`Monitor { id, rx }` instead of a bare `Receiver<Down>`.
|
`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
|
- **Pid = (index, generation);** stale handles caught by generation mismatch in
|
||||||
`slot()/slot_mut()`. The monitor `NoProc` path relies on this.
|
`slot()/slot_mut()`. The monitor `NoProc` path relies on this.
|
||||||
- **No `select`, no unified per-process mailbox.** Why the supervisor uses the
|
- **No `select`, no unified per-process mailbox.** Why the supervisor uses the
|
||||||
single `supervisor_channel` funnel rather than N monitor channels, and why
|
single `supervisor_channel` funnel rather than N monitor channels. trap_exit
|
||||||
trap_exit/selective-receive delivery need an explicit channel decision.
|
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
|
- **Cooperative-only**: preemption and (future) cancellation both depend on the
|
||||||
actor reaching `check!()`/yield/alloc/blocking points.
|
actor reaching `check!()`/yield/alloc/blocking points.
|
||||||
- `run()` is single-thread (`Config::exact(1)`); tests rely on deterministic
|
- `run()` is single-thread (`Config::exact(1)`); tests rely on deterministic
|
||||||
|
|||||||
Reference in New Issue
Block a user