roadmap: 013/014 shipped, gen_server time patterns up next

Move typed addressable mailboxes (RFC 013 directory rework + RFC 014 typed
addressing/producers/naming/root-exit teardown) into Shipped, with the final
phase's delivered surface enumerated. Retarget Up next to the gen_server
time-related patterns: send_after/cancel_timer substrate + the OTP time idioms
(idle/receive timeout, periodic tick/heartbeat, debounce/backoff) on the v0.8
server loop. Fold the duplicate Later send_after bullet into Up next, and mark
the "app actor blocks AllDone" Look-into as addressed by the root-exit teardown.
This commit is contained in:
smarm-agent
2026-06-18 12:32:12 +00:00
parent ecb0835aa7
commit 5cb7f6a491
+74 -23
View File
@@ -50,24 +50,71 @@ Consequences:
--- ---
## Up next — RFC 013: typed addressable mailboxes (send to pid / name) ## Typed addressable mailboxes ✅ SHIPPED (RFC 013 + RFC 014)
Design agreed; full spec persisted in SMARM (`rfc_013-typed-addressable-mailboxes.md`) The unblocker several later items quietly assumed: a `Pid` is now messageable.
and at `/root/`. The unblocker several later items quietly assume: today a `Pid` RFC 013 reworked `registry.rs` from a name↔pid *bimap* into a name→**live
isn't messageable, so `whereis → Pid` is near-useless and there is no send-to-pid/ mailbox** directory off the cold leaf; RFC 014 layered the typed addressing and
name primitive. RFC 013 reworks `registry.rs` from a name↔pid *bimap* into a producers on top. Two addressing modes — `Pid<A>` (direct, identity-bound) and
name→**live mailbox** directory off the cold leaf, with two typed addressing modes `Name<M>` (durable, re-resolving, location-transparent) — compile-time typing
`Pid<A>` (direct, identity-bound) and `Name<M>` (durable, re-resolving, preserved via phantom tokens over *contained* `Box<dyn Any>` erasure (the
location-transparent). Compile-time typing is preserved via phantom tokens over global-enum alternative was rejected: it breaks library-extensibility for
*contained* `Box<dyn Any>` erasure; the global-enum alternative was rejected out-of-crate actors). Channel store keyed by message `TypeId` in every path.
because it breaks library-extensibility (users outside the crate define actors).
Sequenced **before `send_after`** (whose `Dest` becomes a `Pid<A>` / `Name<M>` Delivered surface:
resolved on fire), is the foundation for clustering's "addressed by name not pid" - **Sends:** `send_to` (`Pid<A>`), `send` (`Name<M>`), `send_dyn` (bare-pid
migratable gen_servers, and makes the registry observer-ready for introspection escape hatch, names the message type) — earlier 014 phases.
(entry carries pid + `&'static str` msg-type). Note this extends — does not retire - **Producers & discovery** (final phase, `a866e34`): `spawn_addr` (typed-path
— the standing "select exists; a unified per-process mailbox still does not" producer; parent-side inbox publish so an immediate `send_to` resolves, no
invariant: 013 adds addressable *delivery*, not a unified inbox; multi-port stays race on the body); `lookup_as` / `pick_as` / `members_as` (unchecked-but-sound
`select` composition over several named channels. re-type of an erased pid — a wrong `A` degrades to `NoChannel`, never
misdelivery); `dispatch` (pick-a-live-member-and-send, `SendError::NoMember`
on empty pool).
- **By-name gen_servers** (final phase): `ServerName<G>` over the existing
typed-channel store (keyed by `TypeId::of::<Envelope<G>>()`, so `Envelope`
stays private and no separate directory is needed); type-state
`NamedServerBuilder<G>` (fallible `start`, `NameTaken`) leaving the infallible
`ServerBuilder::start` untouched; free `call` / `cast` / `whereis_server`;
`ServerRef::shutdown` + free `shutdown` as the sys-style synchronous stop.
- **Root-exit teardown** (final phase): the run's initial actor is the root;
when it exits, the scheduler's idle verdict stops the parked-forever remainder
(deferred past the queue drain, so actors with in-flight work finish rather
than unwinding on the stop). Closes the "app actor blocks AllDone" stall — see
Look into, below.
Extends — does not retire — the "select exists; a unified per-process mailbox
still does not" invariant: 014 adds addressable *delivery*, not a unified inbox;
multi-port stays `select` composition over named channels. Examples:
`examples/{typed_actor,named_genserver,worker_pool}.rs`. Earlier-phase commits
and the full 013/014 history in git.
---
## Up next — gen_server time-related patterns
Now unblocked (RFC 013/014 shipped: a timer's `Dest` resolves to a `Pid<A>` /
`Name<M>` *on fire*, not a bespoke channel/closure). Two layers:
**Substrate — `send_after` / `cancel_timer`.** Message-delivery timer on the
existing min-heap (`timer.rs`): deliver a value to an address at a deadline,
cancellable. Small, and the thing the gen_server idioms below have no clean
expression without today.
**The gen_server patterns themselves** (the headline). The OTP time vocabulary,
expressed against the v0.8 server loop and its `select` arm priority:
- **Idle / receive timeout** — a callback directive that arms a one-shot so the
server gets a `timeout` if its inbox stays quiet (session expiry, idle
shutdown). Folds into the existing arm-priority ordering.
- **Periodic tick / heartbeat** — a `ServerCtx` timer arm that re-arms, for
liveness probes and metrics flushes.
- **Debounce / retry backoff** — arm-and-cancel against the latest event;
exponential re-arm on failure.
Call deadlines already exist in part (`CallTimeoutError`); fold them in rather
than reinvent. Needs an RFC: the timer-arming surface on `ServerCtx`, the
timeout-directive shape, and how a fired timer interacts with `handle_info` /
arm priority. Land the `send_after` substrate first; `gen_statem` state timeouts
(Low priority) then sit on the same mechanism.
--- ---
@@ -97,11 +144,8 @@ profiling spike (where do the cycles actually go per park/unpark round-trip)
and then an RFC before it can be scheduled. and then an RFC before it can be scheduled.
#### send_after / cancel_timer #### send_after / cancel_timer
Message-delivery timer on the existing min-heap (`timer.rs`): deliver a value to a Promoted to **Up next** (gen_server time-related patterns) — it's the substrate
channel at a deadline, cancellable. Unlocks the gen_server idioms with no clean that layer lands on, now unblocked by RFC 013/014.
expression today — heartbeat, debounce, retry backoff, session expiry. Small.
**Depends on RFC 013** (sequenced ahead of it): `Dest` resolves to a `Pid<A>` /
`Name<M>` *on fire*, not a bespoke channel/closure — see 013's send_after note.
#### Introspection — process_info / get_state / tree dump #### Introspection — process_info / get_state / tree dump
`trace.rs` is the seed. What an actor is parked on, queue depth, stack size; a `trace.rs` is the seed. What an actor is parked on, queue depth, stack size; a
@@ -182,9 +226,16 @@ RFCs, not one. Spine settled in discussion; decisions still open:
## Look into ## Look into
### app actors block AllDone; no external stop path ### app actors block AllDone; no external stop path — ADDRESSED (RFC 014 root-exit teardown)
Agent working on urus (see same git server as smarm) reported a lazily spawned actor never returning, blocking program shutdown. Maybe we should do something about it. Agent worked around it by giving the actor an atomic bool to spin on. See urus example crud for exact impl. Agent working on urus (see same git server as smarm) reported a lazily spawned actor never returning, blocking program shutdown. Maybe we should do something about it. Agent worked around it by giving the actor an atomic bool to spin on. See urus example crud for exact impl.
**Update (RFC 014):** root-exit teardown stops the parked-forever remainder when
the root actor exits, so a lazily spawned daemon no longer wedges shutdown on
`live_actors > 0`; `ServerRef::shutdown` (+ free `shutdown`) is the explicit stop
path the atomic-bool workaround stood in for. Re-check the urus crud repro to
confirm the workaround can be retired (the teardown is cooperative — an actor in
a tight loop with no observation point still can't be stopped).
--- ---
## Invariants & gotchas (respect these across all cycles) ## Invariants & gotchas (respect these across all cycles)