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 |
|
| `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 |
|
| `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`) |
|
| `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 |
|
| `registry` | `register`/`whereis`/`name_of`: name ↔ pid bimap; lazy generation-checked cleanup |
|
||||||
|
|
||||||
## Quick taste
|
## 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
|
Goal: spend the `select` primitive on the server loop — out-of-band messages
|
||||||
(`handle_info`) and monitor `Down`s (`handle_down`) dispatched alongside
|
(`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 arms drop silently** (the closed-arm-is-ready-forever gotcha).
|
||||||
Closed inbox still means graceful shutdown, unchanged.
|
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`
|
`type Info` + `handle_info` (no-op default) on `GenServer`; `ServerBuilder`
|
||||||
(`Server::new(state).with_info(rx).under(sup).start()`) so start variants
|
(`Server::new(state).with_info(rx).under(sup).start()`) so start variants
|
||||||
don't multiply; `start`/`start_under` stay as wrappers. Loop becomes
|
don't multiply; `start`/`start_under` stay as wrappers. Loop becomes
|
||||||
`select` over `[infos…, inbox]`, dispatching by arm index.
|
`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
|
`handle_down` (no-op default), `ServerCtx`/`Watcher`, the control arm, and
|
||||||
the loop's live `Vec<Monitor>`. A `Down` retires its arm (monitors are
|
the loop's live `Vec<Monitor>`. A `Down` retires its arm (monitors are
|
||||||
one-shot). Integration test: the motivating worker-pool pattern — a server
|
one-shot). Integration test: the motivating worker-pool pattern — a server
|
||||||
that spawns workers in a handler, watches them, and reacts to their deaths.
|
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
|
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
|
and the kernel registration; the stale entry then fails every future
|
||||||
`wait_*` on that fd with `AlreadyExists` (the defensive bare-DEL in
|
`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
|
(an entry consumed by a racing `FdReady` means the fd may already be
|
||||||
re-registered by someone else — leave it alone).
|
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
|
## 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
|
- **`select` exists; a unified per-process mailbox still does not.** The
|
||||||
supervisor keeps its single `supervisor_channel` funnel; `recv_match`
|
supervisor keeps its single `supervisor_channel` funnel; `recv_match`
|
||||||
stays per-channel. `select` composes channels at the wait, not into one
|
stays per-channel. `select` composes channels at the wait, not into one
|
||||||
queue — the mailbox-merge decision remains open (see Later:
|
queue — gen_server's `handle_info`/`handle_down` (v0.8) are built on
|
||||||
gen_server handle_info).
|
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
|
- **Cooperative-only.** Preemption and cancellation both depend on the actor
|
||||||
reaching `check!()`/yield/alloc/blocking points.
|
reaching `check!()`/yield/alloc/blocking points.
|
||||||
- **Lock order is Leaf → Channel, one of each at most** (debug-asserted in
|
- **Lock order is Leaf → Channel, one of each at most** (debug-asserted in
|
||||||
|
|||||||
Reference in New Issue
Block a user