Wire eviction to actor death via the monitor subsystem, using drain-on-
contact (no scheduler hot-path hook).
- Each membership now carries its own Monitor alongside the group entry;
join() installs monitor(pid) (registration races finalize_actor under the
cold lock, as every monitor does) and a redundant join tears its extra
monitor back down.
- reap_group: every group op drains the touched group's monitors with a
non-blocking try_recv (a delivered Down or closed channel = dead) and, on
the first death, sweeps that pid out of *every* group via remove_where —
so a death vanishes from all its groups on next contact.
- Lock discipline: monitor()/demonitor() (cold = Leaf) run outside the group
lock; try_recv (Channel) runs under it, permitted by the Leaf->Channel
order; evicted/rejected monitors are dropped after the lock releases so no
receiver-drop wakeup runs under it. Validated by the debug-build lock-order
checker passing under the integration tests.
- remove_where now returns the evicted monitors (for deferred drop) and
preserves intra-group insertion order.
- Tests: unit (synthetic monitors) for reap live/dead/closed + sweep;
integration (real actors) for death-vanishes-from-every-group, pick on an
all-dead group, peer survival, leave isolation, dead-at-join eviction.
Outstanding: a miri pass (cargo +nightly miri test --lib pg::) is untried —
first build exceeds the sandbox window and the futex RawMutex may need a miri
shim; the mechanical Leaf->Channel checker is the primary soundness guard.
Add a process-groups module (name -> multiset<Member>) sibling to the
registry, plus the cluster-shaped identity it is keyed on.
- NodeId/Incarnation u32 newtypes; Member { node, incarnation, pid } at
final (BEAM NEW_PID_EXT-shaped) layout.
- Config::node_id/incarnation builder setters beside wake_slot, defaulting
to a fixed single-node value; threaded through RuntimeInner::new.
- process_groups: RawMutex<ProcessGroups> on RuntimeInner, Leaf class,
mirroring the registry field.
- remove_where: the one dumb predicate-eviction primitive (no liveness wired
yet; first caller is the Phase 2 death hook).
- Public surface (join/leave/members/pick) in pre-liveness/pre-monitor form,
Pid-shaped; node/incarnation filled from runtime identity.
- Structural unit tests on synthetic members: idempotent join, multiset
semantics across groups, generation-distinct members, leave + empty-group
pruning, predicate sweep, incarnation-sweep shape.
- 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.
The spinning experiment is excised from master. Drops the two dedicated v0.9
sections describing it. Inline RFC-004 mentions in the v0.9 intro and the
queue-topology decision record are intentionally left for a manual short-term
-plan pass.
Carries forward the non-spin parts of the dropped spin-tooling commit:
- .gitignore: __pycache__/ and *.pyc
- sweep.py: generic vs-tokio comparison summary (no spin_sweep coupling)
The spin_sweep README docs from that commit are intentionally left behind.
The profile was captured against the spin-enabled build. With the RFC 004
spinning experiment excised from master, the ~12% hot-path futex_wake it
attributed to the spinning submit-rule no longer exists (idle wait is back to
thread::sleep). Add a provenance note, mark the futex_wake row/finding as
excised spin machinery, and drop the now-moot 'gate the submit wake' lead.
Spin-independent findings (shims ~1%, schedule_loop + run-queue ~33%) unchanged.