pg: Phase 1 — identity & storage substrate (RFC 012)

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.
This commit is contained in:
smarm-agent
2026-06-15 16:41:30 +00:00
parent 4d4a2a6c9b
commit b78311bc88
3 changed files with 360 additions and 0 deletions
+43
View File
@@ -155,6 +155,8 @@ pub struct Config {
stack_pool_cap: usize,
max_actors: usize,
wake_slot: bool,
node_id: crate::pg::NodeId,
incarnation: crate::pg::Incarnation,
}
impl Config {
@@ -168,6 +170,8 @@ impl Config {
stack_pool_cap: n * 4,
max_actors: DEFAULT_MAX_ACTORS,
wake_slot: false,
node_id: crate::pg::DEFAULT_NODE_ID,
incarnation: crate::pg::DEFAULT_INCARNATION,
}
}
@@ -185,6 +189,8 @@ impl Config {
stack_pool_cap: max * 4,
max_actors: DEFAULT_MAX_ACTORS,
wake_slot: false,
node_id: crate::pg::DEFAULT_NODE_ID,
incarnation: crate::pg::DEFAULT_INCARNATION,
}
}
@@ -241,6 +247,23 @@ impl Config {
self
}
/// This runtime's node identity (RFC 012). Defaults to a fixed single-node
/// value; clustering (RFC 010) will supply a real one. Threaded through pg
/// storage/eviction so the process-group public API never changes to
/// acquire it.
pub fn node_id(mut self, id: impl Into<crate::pg::NodeId>) -> Self {
self.node_id = id.into();
self
}
/// This runtime's incarnation epoch (RFC 012) — the BEAM `Creation` analogue
/// that separates a crashed node from its restart. Defaults to a fixed
/// single-node value; constant for the life of a run.
pub fn incarnation(mut self, inc: impl Into<crate::pg::Incarnation>) -> Self {
self.incarnation = inc.into();
self
}
/// The number of scheduler threads this config resolves to.
pub fn resolved_thread_count(&self) -> usize {
if let Some(e) = self.exact {
@@ -265,6 +288,8 @@ impl Default for Config {
stack_pool_cap: avail * 4,
max_actors: DEFAULT_MAX_ACTORS,
wake_slot: false,
node_id: crate::pg::DEFAULT_NODE_ID,
incarnation: crate::pg::DEFAULT_INCARNATION,
}
}
}
@@ -507,6 +532,17 @@ pub(crate) struct RuntimeInner {
/// with any other lock; liveness checks under it read only the atomic
/// slot word.
pub(crate) registry: RawMutex<crate::registry::Registry>,
/// Runtime identity (RFC 012). Read-only after init — one node, one fixed
/// incarnation until clustering (RFC 010) supplies real values. Carried
/// like `wake_slot`; pg fills these into every `Member` so the public
/// surface stays Pid-shaped.
pub(crate) node_id: crate::pg::NodeId,
pub(crate) incarnation: crate::pg::Incarnation,
/// Process groups: `name -> multiset<Member>` (RFC 012). RawMutex Leaf,
/// exactly like `registry`: never held with any other lock; liveness
/// checks under it read only the atomic slot word, and the eviction path
/// keeps it off the send path.
pub(crate) process_groups: RawMutex<crate::pg::ProcessGroups>,
/// Recycled stacks waiting to be reused by the next spawn.
pub(crate) stack_pool: RawMutex<Vec<crate::stack::Stack>>,
/// Maximum number of stacks to retain in the pool.
@@ -521,6 +557,8 @@ impl RuntimeInner {
stack_pool_cap: usize,
max_actors: usize,
wake_slot: bool,
node_id: crate::pg::NodeId,
incarnation: crate::pg::Incarnation,
) -> Arc<Self> {
let stats = (0..thread_count).map(|_| SchedulerStats::new()).collect();
let slots: Box<[Slot]> = (0..max_actors).map(|_| Slot::vacant()).collect();
@@ -542,6 +580,9 @@ impl RuntimeInner {
timeslice_cycles,
wake_slot,
registry: RawMutex::new(crate::registry::Registry::new()),
node_id,
incarnation,
process_groups: RawMutex::new(crate::pg::ProcessGroups::new()),
stack_pool: RawMutex::new(Vec::new()),
stack_pool_cap,
})
@@ -712,6 +753,8 @@ pub fn init(config: Config) -> Runtime {
config.stack_pool_cap,
config.max_actors,
config.wake_slot,
config.node_id,
config.incarnation,
),
thread_count: n,
}