feat(cluster): RFC 010 c10–c16, follow-ups and Phase 6 (squash of 16ef583..d9c62a8)

Tree snapshot of d9c62a8 (2026-08-18). The 20 source commits between
16ef583 (c9) and d9c62a8 were never pushed and the clone that held them
was lost; this commit carries their combined tree verbatim so the build
history stays auditable from the c1–c9 commits below it. Original
hashes as recorded in the session handoff:

  c10  f03e94d  pid targeting + auto-serialization (RemotePid, D14 name
                on the wire); Phase 3 gate
  c11  7ef4bad  DownReason::Disconnected, wire tag 5
  c12  d124162  remote monitors (Monitor/Demonitor/Down frames)
  c13  9de967b  connection-loss synthesis (A+B: Monitors::teardown +
                unread-command Disconnected); Phase 4 gate
  c14  7e822b7  eager pg eviction (reaper actor, ReaperInboxes)
       dbe1a22  InboundVerdict::label(), trace::Event::ClusterInbound
       31a9877  tests/channel.rs monitor-churn target gated on `go`
       653559e  Discovery::Withdrawn{name, addr}
  c15  b41d76e  distributed pg: Sync on NodeUp, Join/Leave broadcast,
                NodeDown sweep, members_all; PgMsg wire type
  c16  fafa881  pick_any / dispatch_any; Phase 5 complete
  Phase 6 Tier A:
       195c73e  p4  NodeEvent::NodeDown(NodeInfo)
       48fd766  p1  connector Candidate{name, addr, state}
       ce8cf99  p2+p7 conn.rs select arms as Vec<Arm>; Outbound::Drained
       bf24988  p6  RemotePid::from_local -> Option
       9ae0380  p3  PeerStanding{Free, Claimed, Dialing}
       c7d62a1  p11 cluster::Timing knobs, threaded by value
       46f171d  p11 cluster_disconnect un-ignored on SMARM_FAST_TIMING
  Phase 6 Tier B:
       391a9ae  p5  cluster::RemoteDownReason{Local, Disconnected};
                    DownReason::Disconnected removed from core
       7ddd908  p9  pg ctl channel unconditional, one cfg seam at spawn
       d9c62a8      PeerNameMismatch parks the candidate; ClusterDial trace

Verified at d9c62a8: default 361/0, cluster 448/0, clippy --lib on
default / cluster / cluster+smarm-trace, fmt, 10x flake on
cluster_dial_mismatch, 5x on cluster_pg.
This commit is contained in:
claude-asm-audit
2026-08-18 16:00:00 +02:00
parent 16ef583455
commit 4c0e42152f
32 changed files with 4202 additions and 578 deletions
+49 -11
View File
@@ -81,10 +81,45 @@ pub enum Frame {
},
Down {
monitor_id: u64,
reason: DownReason,
reason: RemoteDownReason,
},
}
/// Why a remotely-monitored actor is reported down: either the target's own
/// terminal [`DownReason`] as its node recorded it, or the *link* to that
/// node was lost (or absent) — which says nothing about the actor itself.
///
/// This is the cluster-side widening of `DownReason` (p5): `Disconnected`
/// is a fact about a connection, never about a local actor, so it lives
/// here rather than in the core enum — a local `Down` can never carry it,
/// and matches on `DownReason` stay exhaustive over actor outcomes only.
/// On the wire `Local(r)` uses `r`'s tag and `Disconnected` is tag 5,
/// bound since c11; no peer emits it today (a lost link is synthesized
/// locally), but the codec honours it both ways.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RemoteDownReason {
/// The target itself terminated; the peer reported this reason.
Local(DownReason),
/// The link to the target's node was lost or was never up.
Disconnected,
}
impl RemoteDownReason {
/// The actor's own reason, if this was not a link loss.
pub fn local(self) -> Option<DownReason> {
match self {
RemoteDownReason::Local(r) => Some(r),
RemoteDownReason::Disconnected => None,
}
}
}
impl From<DownReason> for RemoteDownReason {
fn from(r: DownReason) -> Self {
RemoteDownReason::Local(r)
}
}
// Frame tags. 0 is deliberately unassigned so an all-zero buffer never parses.
const TAG_HELLO: u8 = 1;
const TAG_HELLO_ACK: u8 = 2;
@@ -101,11 +136,12 @@ const REJ_HASH_MISMATCH: u8 = 1;
const REJ_NAME_TAKEN: u8 = 2;
const REJ_PROTO_VERSION: u8 = 3;
// DownReason tags. c11 adds `Disconnected = 5`; do not reuse tags.
// DownReason tags. Do not reuse tags.
const DR_EXIT: u8 = 1;
const DR_PANIC: u8 = 2;
const DR_STOPPED: u8 = 3;
const DR_NOPROC: u8 = 4;
const DR_DISCONNECTED: u8 = 5;
/// Frame could not be encoded. The output buffer is left exactly as it was.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -272,10 +308,11 @@ impl Frame {
out.push(TAG_DOWN);
put_u64(out, *monitor_id);
out.push(match reason {
DownReason::Exit => DR_EXIT,
DownReason::Panic => DR_PANIC,
DownReason::Stopped => DR_STOPPED,
DownReason::NoProc => DR_NOPROC,
RemoteDownReason::Local(DownReason::Exit) => DR_EXIT,
RemoteDownReason::Local(DownReason::Panic) => DR_PANIC,
RemoteDownReason::Local(DownReason::Stopped) => DR_STOPPED,
RemoteDownReason::Local(DownReason::NoProc) => DR_NOPROC,
RemoteDownReason::Disconnected => DR_DISCONNECTED,
});
}
}
@@ -364,13 +401,14 @@ impl Frame {
TAG_DOWN => Frame::Down {
monitor_id: r.u64()?,
reason: match r.u8()? {
DR_EXIT => DownReason::Exit,
DR_PANIC => DownReason::Panic,
DR_STOPPED => DownReason::Stopped,
DR_NOPROC => DownReason::NoProc,
DR_EXIT => RemoteDownReason::Local(DownReason::Exit),
DR_PANIC => RemoteDownReason::Local(DownReason::Panic),
DR_STOPPED => RemoteDownReason::Local(DownReason::Stopped),
DR_NOPROC => RemoteDownReason::Local(DownReason::NoProc),
DR_DISCONNECTED => RemoteDownReason::Disconnected,
t => {
return Err(DecodeError::UnknownEnumTag {
what: "DownReason",
what: "RemoteDownReason",
tag: t,
})
}