docs(roadmap,readme): v0.8 complete — record outcomes and deviations
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user