docs(roadmap): v0.7 plan — select via epoch-stamped consuming wakes
This commit is contained in:
+77
-3
@@ -152,6 +152,79 @@ Landed as two primitives, *without* the sketched monitor machinery:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v0.7 — select, on epoch-stamped consuming wakes
|
||||||
|
|
||||||
|
Goal: cross-channel wait (`select`) without surrendering the precise-wake
|
||||||
|
invariant. Decision record: the alternative ("wakes are hints", loop-and-
|
||||||
|
recheck at every park site) was evaluated and rejected — it distributes the
|
||||||
|
soundness obligation across every present and future park site and turns
|
||||||
|
every stale wake into a wasted reschedule. Instead the slot protocol itself
|
||||||
|
gains *wait identity*: a park-epoch packed into the spare bits of the slot
|
||||||
|
word, so that a stale unpark dies at one failed CAS in the waker.
|
||||||
|
|
||||||
|
Protocol (the consuming-wake rules):
|
||||||
|
- Word becomes `(gen << 32) | (epoch << 8) | state` — 24-bit epoch in the
|
||||||
|
previously unused bits; every transition still one CAS, gen check still
|
||||||
|
atomic with it.
|
||||||
|
- `begin_wait` (actor-side, before registering with any waker) bumps the
|
||||||
|
epoch and returns it; registrations carry `(pid, epoch)`.
|
||||||
|
- Every successful wake transition **consumes** the epoch (bumps it):
|
||||||
|
`Parked(e) → Queued(e+1)`, `Running(e) → RunningNotified(e+1)`. A second
|
||||||
|
unpark stamped with the same epoch can never land — at most one wake per
|
||||||
|
wait, by construction. This is what makes select's *loser* arms harmless
|
||||||
|
without a cancellation pass: their stale registrations fire one futile CAS
|
||||||
|
on next send and self-clean.
|
||||||
|
- `unpark_at(pid, epoch)` = epoch-matched wake. Bare `unpark(pid)` remains as
|
||||||
|
the wildcard and is reserved for **terminal** wakes (`request_stop`), which
|
||||||
|
never return control to the code that parked.
|
||||||
|
- One-shot park sites (`Mutex::lock_timeout`, `sleep`, `block_on_io`,
|
||||||
|
`wait_fd`) stay exactly as written: precise wakes are preserved, so a wake
|
||||||
|
still *means* something.
|
||||||
|
|
||||||
|
### 1. Slot-word epoch + consuming unparks
|
||||||
|
`slot_state.rs`: repack, `begin_wait`, epoch-matched/consuming `unpark`,
|
||||||
|
epoch-preserving claim/yield/park transitions. Loom: existing theorems
|
||||||
|
re-proved on the new word + a new stale-epoch theorem (an unpark stamped with
|
||||||
|
a consumed epoch can never enqueue *or* notify). Plumbing only — no caller
|
||||||
|
stamps yet; behaviour identical.
|
||||||
|
|
||||||
|
### 2. Stamp every registration-based wake; retire per-primitive wait seqs
|
||||||
|
The epoch IS the wait identity, so `channel::Inner.{cur_wait,next_wait_seq}`
|
||||||
|
and `mutex`'s per-wait `seq` are subsumed: registrations become
|
||||||
|
`(pid, epoch)`, `TimerTarget::on_timeout(pid, epoch)`, wakers call
|
||||||
|
`unpark_at`. Sleep timers, io completions (`Blocking` + `FdReady`) and join
|
||||||
|
waiters stamped likewise. End-state invariant, auditable in one sentence:
|
||||||
|
**the only wildcard wake is `request_stop`.**
|
||||||
|
|
||||||
|
### 3. `select` — ready-index wait over multiple receivers
|
||||||
|
`select(&[&dyn Selectable]) -> usize`: register self in each arm's
|
||||||
|
`parked_receiver` (sequentially — the park protocol makes cross-arm atomicity
|
||||||
|
unnecessary), park once, wake, scan, return the first ready index; caller
|
||||||
|
`try_recv`s it. Sound as ready-index *because channels are single-receiver*:
|
||||||
|
nobody can steal between ready and recv. A closed arm counts as ready (its
|
||||||
|
`try_recv` reports the closure). Arms are scanned in order — priority order,
|
||||||
|
documented, like BEAM receive clauses; no fairness promise. No cancel pass
|
||||||
|
(see protocol above). Drop-guard deregistration on the unwind path only, to
|
||||||
|
keep tests' single-parked-receiver asserts honest.
|
||||||
|
|
||||||
|
### 4. `select_timeout`
|
||||||
|
A `WaitTimeout` timer stamped with the select's epoch, whose target is a
|
||||||
|
stateless `unpark_at`. The woken selector classifies the wake from state
|
||||||
|
alone: some arm ready → that arm; none ready → with precise wakes the timer
|
||||||
|
is the only remaining stamped waker → `None`. No flag, no shared waiter
|
||||||
|
struct, no registration to leak. The timer entry expires as a stale-epoch
|
||||||
|
no-op when an arm wins, per the no-cancellation convention in `timer.rs`.
|
||||||
|
|
||||||
|
### 5. Docs + integration proof
|
||||||
|
README module table, gotchas rewrite (the "No `select`" bullet becomes the
|
||||||
|
consuming-wake invariant), and an integration test of the motivating
|
||||||
|
pattern: a server selecting over its inbox and a monitor `Down` channel.
|
||||||
|
|
||||||
|
Unblocked but deliberately out of scope: `gen_server::handle_info` (now an
|
||||||
|
API-design question, not a runtime question — see Later).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Later
|
## Later
|
||||||
|
|
||||||
### Tunable scheduler idle policy (RFC 004)
|
### Tunable scheduler idle policy (RFC 004)
|
||||||
@@ -175,9 +248,10 @@ Revisit with a segmented slab (array of `AtomicPtr<Segment>`, doubling segment
|
|||||||
sizes, append-only) once the cap is actually hit. Do not let it calcify.
|
sizes, append-only) once the cap is actually hit. Do not let it calcify.
|
||||||
|
|
||||||
### gen_server: handle_info
|
### gen_server: handle_info
|
||||||
Needs a cross-channel mailbox merge (call/cast inbox + out-of-band signals) —
|
Runtime prerequisite landed in v0.7 (`select`). What remains is API design:
|
||||||
the still-unmade decision between a unified per-actor mailbox and per-channel
|
the `Info` message type, who owns the extra channels, whether monitor `Down`s
|
||||||
`recv_match` composition.
|
auto-forward into the server loop. Decide against real consumers, not
|
||||||
|
speculatively.
|
||||||
|
|
||||||
### IO fd hygiene on actor death
|
### IO fd hygiene on actor death
|
||||||
Pre-existing v0.2 TODO in `io.rs`: fds registered with epoll for a dead actor
|
Pre-existing v0.2 TODO in `io.rs`: fds registered with epoll for a dead actor
|
||||||
|
|||||||
Reference in New Issue
Block a user