docs(roadmap,readme): v0.8 complete — record outcomes and deviations

This commit is contained in:
Claude
2026-06-10 15:08:04 +00:00
parent f6969e538b
commit 0e3df6ec5b
2 changed files with 20 additions and 7 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ convenience wrapper around `runtime::init(Config::exact(1)).run(f)`.
| `supervisor` | `Signal::Exit`/`Panic`/`Stopped` funnelled to a parent; `OneForOne`/`OneForAll`/`RestForOne` strategies + restart-intensity cap |
| `monitor` | `monitor(pid)``Monitor { id, target, rx }`; one-shot `Down` via `rx`; `demonitor(&m)` tears one registration down; unidirectional death notice |
| `link` | bidirectional `link`/`unlink`; abnormal death propagates (cooperative stop, or an `ExitSignal` message under `trap_exit`) |
| `gen_server` | `call`/`call_timeout` (sync request-reply) / `cast` (async) over one inbox; `ServerRef` + `init`/`terminate` hooks; server-down via channel closure |
| `gen_server` | `call`/`call_timeout` (sync request-reply) / `cast` (async) over one inbox; `handle_info` over static info arms + `handle_down` via `Watcher`-fed monitors, selected ahead of the inbox; `ServerRef`/`ServerBuilder` + `init`/`terminate` hooks; server-down via channel closure |
| `registry` | `register`/`whereis`/`name_of`: name ↔ pid bimap; lazy generation-checked cleanup |
## Quick taste
+19 -6
View File
@@ -244,7 +244,7 @@ API-design question, not a runtime question — see Later).
---
## v0.8 — gen_server: handle_info / handle_down + io fd hygiene
## v0.8 — gen_server: handle_info / handle_down + io fd hygiene ✅ DONE
Goal: spend the `select` primitive on the server loop — out-of-band messages
(`handle_info`) and monitor `Down`s (`handle_down`) dispatched alongside
@@ -266,19 +266,19 @@ Design decisions (settled up front):
- **Closed arms drop silently** (the closed-arm-is-ready-forever gotcha).
Closed inbox still means graceful shutdown, unchanged.
### 1. handle_info — static info arms in the server loop
### 1. handle_info — static info arms in the server loop ✅ (`e5d1b3b`)
`type Info` + `handle_info` (no-op default) on `GenServer`; `ServerBuilder`
(`Server::new(state).with_info(rx).under(sup).start()`) so start variants
don't multiply; `start`/`start_under` stay as wrappers. Loop becomes
`select` over `[infos…, inbox]`, dispatching by arm index.
### 2. handle_down — ServerCtx::watcher + control arm
### 2. handle_down — ServerCtx::watcher + control arm ✅ (`24b95c9`)
`handle_down` (no-op default), `ServerCtx`/`Watcher`, the control arm, and
the loop's live `Vec<Monitor>`. A `Down` retires its arm (monitors are
one-shot). Integration test: the motivating worker-pool pattern — a server
that spawns workers in a handler, watches them, and reacts to their deaths.
### 3. io fd hygiene — DEL on the unwind path
### 3. io fd hygiene — DEL on the unwind path ✅ (`f6969e5`)
A stopped actor unwinding out of `wait_fd`'s park leaks its `waiters` entry
and the kernel registration; the stale entry then fails every future
`wait_*` on that fd with `AlreadyExists` (the defensive bare-DEL in
@@ -289,6 +289,17 @@ waiters entry iff it is still `(me, epoch)` and only then DELs the fd
(an entry consumed by a racing `FdReady` means the fd may already be
re-registered by someone else — leave it alone).
### Deviations from the plan
- **The plain-inbox fast path narrowed.** It now also requires the control
arm closed, so every server pays one select on its first iteration (the
ctx drops after `init`; a state that didn't clone the Watcher closes the
arm there and then). Servers that DO hold a Watcher select forever — the
price of dynamic watch.
- **The fd leak was worse than the v0.2 caveat admitted.** Not "at most one
stale wakeup": the stale `waiters` entry failed every future `wait_*` on
that fd with AlreadyExists, permanently (the defensive DEL sat behind the
`contains_key` check). The regression test exhibits it as a hang.
---
## Later
@@ -348,8 +359,10 @@ hardware. Build + run full test suite on an aarch64 device; check
- **`select` exists; a unified per-process mailbox still does not.** The
supervisor keeps its single `supervisor_channel` funnel; `recv_match`
stays per-channel. `select` composes channels at the wait, not into one
queue — the mailbox-merge decision remains open (see Later:
gen_server handle_info).
queue — gen_server's `handle_info`/`handle_down` (v0.8) are built on
exactly that composition, with documented arm priority (downs → control
→ infos → inbox) instead of mailbox FIFO. A hot higher-priority arm
starves lower ones by design; that's the contract.
- **Cooperative-only.** Preemption and cancellation both depend on the actor
reaching `check!()`/yield/alloc/blocking points.
- **Lock order is Leaf → Channel, one of each at most** (debug-asserted in