diff --git a/ROADMAP.md b/ROADMAP.md index 1353657..723dc86 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -244,6 +244,53 @@ API-design question, not a runtime question — see Later). --- +## v0.8 — gen_server: handle_info / handle_down + io fd hygiene + +Goal: spend the `select` primitive on the server loop — out-of-band messages +(`handle_info`) and monitor `Down`s (`handle_down`) dispatched alongside +calls and casts — and close the v0.2 fd-hygiene hole in `io.rs`. + +Design decisions (settled up front): +- **Info channels are static.** `type Info: Send + 'static` on the trait; a + `Vec>` handed over at start. Users enum their own Info. + Dynamic info subscription is deliberately out: revisit against a consumer. +- **Down forwarding is dynamic, because monitors are.** You monitor a worker + you just spawned inside a handler, so down arms cannot ride the static + list. Mechanism: the loop owns a control arm (`Receiver`); + `init` grows a `&ServerCtx` parameter (breaking, no-op default body) whose + clonable `Watcher` the state stores and calls `watch(monitor)` from any + handler thereafter. Handler signatures otherwise untouched. +- **Priority order: downs → control → infos → inbox.** System messages + preempt; a hot inbox cannot starve a death notice. Within each class, + declaration order (select's documented priority semantics). +- **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 +`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 +`handle_down` (no-op default), `ServerCtx`/`Watcher`, the control arm, and +the loop's live `Vec`. 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 +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 +`epoll_register` sits behind the `contains_key` check, so it never runs). +Fix where the invariant breaks: a drop guard in `wait_fd`, armed after a +successful register, disarmed on normal wake; on unwind it removes the +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). + +--- + ## Later ### Tunable scheduler idle policy (RFC 004) @@ -266,15 +313,7 @@ Fixed slab with a loud assert (`Config::max_actors(n)`, default 16 384). Revisit with a segmented slab (array of `AtomicPtr`, doubling segment sizes, append-only) once the cap is actually hit. Do not let it calcify. -### gen_server: handle_info -Runtime prerequisite landed in v0.7 (`select`). What remains is API design: -the `Info` message type, who owns the extra channels, whether monitor `Down`s -auto-forward into the server loop. Decide against real consumers, not -speculatively. -### IO fd hygiene on actor death -Pre-existing v0.2 TODO in `io.rs`: fds registered with epoll for a dead actor -are not cleaned up. Audit and fix. ### arm-port validation & merge `arm-port` branch carries an AAPCS64 context-switch backend, never run on