feat(cluster): RFC 010 c7a — membership events + view at the manager

node_up/node_down are derived facts of the manager's own register/remove
events, so the membership state lives in the manager (no cross-actor race
between 'connection exists' and 'node is up'); src/cluster/membership.rs is
the consumer surface: NodeEvent/NodeInfo, subscribe(), view(). The conn
table stays private — no consumer touches it (roadmap-binding).

Ratified semantics: subscribe() is snapshot-then-stream — one NodeUp per
live peer is queued before the subscription joins the list, exact because
gen_server handlers are serialized. Dropped subscribers are pruned on the
next emit (closed channel), no monitor needed.

One-viable call, flagged: NodeId is memoized per (name, incarnation) — a
compact local alias for the wire identity, per pg.rs's framing. A reconnect
blip at the same incarnation keeps its id; a restart (new incarnation) gets
a fresh one, so a ghost and its successor are always distinguishable.
Allocation starts at 1; NodeId(0) stays pg::DEFAULT_NODE_ID (self).

Call::Register now carries the whole handshake Peer (the path already has
it; node_up needs incarnation + meta).

tests/cluster_membership.rs 4/0 stable over 5 runs (live up/down over
localhost TCP with commanded and EOF teardown; late-subscriber snapshot +
view agreement; restart-vs-blip id identity; dead-subscriber pruning). All
cluster suites regression-clean; clippy --lib green both configs; fmt
clean; default build compiles.
This commit is contained in:
Claude
2026-08-15 07:07:14 +00:00
parent 112d6b2e65
commit 160967939b
5 changed files with 465 additions and 18 deletions
+2 -2
View File
@@ -76,12 +76,12 @@ pub struct RegisterRefused;
/// actor. A refusal has already stopped the actor and closed the socket.
pub fn spawn_established(framed: FramedConn, peer: Peer) -> Result<Pid, RegisterRefused> {
let (cmd_tx, cmd_rx) = channel();
let name = peer.node_name.clone();
let reg_peer = peer.clone();
let pid = spawn(move || run(framed, peer, cmd_rx)).pid();
match gen_server::call(
MANAGER,
Call::Register {
name,
peer: reg_peer,
pid,
handle: ConnHandle { cmd_tx },
},