roadmap: ship v0.9, promote Process groups to up-next

- Compact v0.9 (Wake-path latency) into Shipped; record the RFC 004 spinning
  excision as a deviation (preserved on branch rfc-004-spinning).
- Drop the v0.7 entry, folding its reference into the Shipped history pointer.
- Promote Process groups from Later into the up-next slot v0.9 vacated;
  Per-switch cost now leads Later/Highest priority.
This commit is contained in:
smarm-agent
2026-06-15 12:39:30 +00:00
parent 6566e2f613
commit 4d4a2a6c9b
+21 -59
View File
@@ -2,20 +2,9 @@
## Shipped (compacted — full cycle plans and deviation records live in git history)
Cycles before v0.7 (v0.4 actor primitives, v0.5 runtime decomposition &
pluggable run queue, v0.6 actor ergonomics): see `git log ROADMAP.md`.
### v0.7 — select, on epoch-stamped consuming wakes ✅
A 24-bit park-epoch packed into the slot word gives every wait an identity:
registrations carry `(pid, epoch)`, every successful wake **consumes** the
epoch, stale wakes die at one failed CAS. Subsumed the per-primitive wait
seqs (channel, mutex, timer); the only wildcard wake left is `request_stop`
(terminal). On top: `select`/`select_timeout` — ready-index wait over many
receivers, priority order, no cancellation pass. Loom theorems re-proved on
the new word. Notable deviations: `retire_wait` for the no-park exit path
(plan missed it); the Drop-guard deregistration was superseded by relaxed
single-receiver asserts; a closed arm is ready *forever* (documented gotcha).
Commits `4913835``00128f3`, `a0a93b6`.
Cycles before v0.8 (v0.4 actor primitives, v0.5 runtime decomposition &
pluggable run queue, v0.6 actor ergonomics, v0.7 select on epoch-stamped
consuming wakes): see `git log ROADMAP.md`.
### v0.8 — gen_server: handle_info / handle_down + io fd hygiene ✅
Spent `select` on the server loop: static info arms (`type Info`,
@@ -27,6 +16,19 @@ entry permanently poisoned the fd). Deviation: the plain-inbox fast path
narrowed; servers holding a `Watcher` select forever.
Commits `e5d1b3b`, `24b95c9`, `f6969e5`.
### v0.9 — Wake-path latency ✅
Attacked per-wake latency with the RFC 005 **wake slot**: a per-scheduler,
thread-local, capacity-one wake cache checked before the shared queue, pushed
only from actor context, slot-then-shared pop with the waker's residual slice
as the starvation bound (`slot_hits`/`slot_displacements`). Benched via the
slot on/off dimension of `rq_runtime` — ping-pong-pairs (win), yield-storm
(regression guard), spawn-storm (neutrality); results annotated in RFC 005.
The RFC 004 spinning-workers experiment, originally scoped here, was evaluated
and **excised** (not worth the code cost; preserved on branch
`rfc-004-spinning`). Also a false-sharing fix (`align(64)` on `SchedulerStats`)
and a termination wake for idle siblings.
Commits `2708042`, `37d9319`, `eddf3fe`.
---
## Decision record — queue topology 🔒 CLOSED (2026-06-10)
@@ -48,51 +50,7 @@ Consequences:
---
## v0.9 — Wake-path latency
Goal: attack per-wake latency — the measured ceiling — with the two specified
mechanisms, each benched against a clean baseline before the next lands.
Order: RFC 005 first (the slot is measured against the just-benched idle
policy), then RFC 004 (measured against the slot-accepted baseline), then an
interaction pass.
Full specs: `rfc_005-wake-slot.md`, `rfc_004-tunable-scheduler-idle-policy.md`
(artefacts.kalsbeek.dev).
### 1. Wake slot (RFC 005)
Per-scheduler, thread-local, capacity-one wake cache, checked before the
shared queue. Runtime-selected via `Config { wake_slot: bool }`, default off
until accepted (one binary benches both arms).
- **Push policy:** slot-eligible iff the wake originates from actor context
(`current_pid().is_some()`). Scheduler-context wakes (timer/IO drain) and
spawns always go shared.
- **Displacement:** newest wake takes the slot, occupant pushed shared (Go
semantics); the branch swap is the fallback if benches look pathological.
- **Pop order:** slot, then shared. Slot-popped actors inherit the waker's
remaining timeslice — a handoff chain is bounded by one slice, so the
shared queue is consulted at least once per slice per scheduler (the
starvation bound, zero new counters).
- **Invariant care:** the slot push replaces `run_queue.push` at the tail of
the `Parked → Queued` CAS; at-most-once-enqueued holds verbatim (pid in
slot ⊕ shared queue). Stall blast radius grows by exactly one actor.
- Counters: `slot_hits`, `slot_displacements`.
### 2. Slot shootout
Extend `rq_runtime`/`bench_rq.sh` with the slot on/off dimension (cheap —
it's a Config knob, not a feature rebuild). Same sweep as the rq shootout.
- **ping-pong-pairs** — target metric; expect the win, single- and
multi-scheduler.
- **yield-storm** — regression guard; yields never touch the slot, any delta
is pop-path overhead.
- **spawn-storm** — neutrality check; spawns bypass the slot by policy.
Acceptance flips the default on and re-baselines for RFC 004.
This is done, see results annotated in RFC005.
---
## Later
### Highest priority
#### Process groups: the primitive pubsub & channels should have sat on
## Up next — Process groups: the primitive pubsub & channels should have sat on
Context: urus is a webserver written on top of smarm to provide a testing target.
A named pid→multiset map with monitor-backed removal: `registry.rs` generalised
from name↔pid *bimap* to name→*multiset*, the death hook reused verbatim. Local
@@ -103,6 +61,10 @@ member), with different netsplit consequences. Urus shipped pubsub/channels pred
and want reframing on top of it. Foundational, so early in the post-v0.9 stack.
Needs an RFC.
---
## Later
### Highest priority
#### Per-switch cost (context shims, epoch protocol)
The shootout's residual: per-wake latency is 0.160.18 µs at N=1 and
0.81.2 µs at N=8+, dominated by the context-switch shims and the epoch