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
+28 -18
View File
@@ -19,11 +19,12 @@ use smarm::cluster::connect::{
HANDSHAKE_TIMEOUT,
};
use smarm::cluster::envelope::{Frame, NodeMeta, RejectReason};
use smarm::cluster::handshake::{HelloCtx, Local};
use smarm::cluster::handshake::{Local, PeerStanding};
use smarm::cluster::manager::{Call, Manager, Reply, MANAGER};
use smarm::cluster::transport::loopback::LoopbackTransport;
use smarm::cluster::transport::tcp::TcpTransport;
use smarm::cluster::transport::{FramedConn, Transport};
use smarm::cluster::Timing;
use smarm::gen_server::{self, GenServerBuilder};
use smarm::pg::Incarnation;
use smarm::{run, sleep};
@@ -100,7 +101,7 @@ fn loopback_happy_path_establishes_both_ends() {
local("node-b"),
|name| {
assert_eq!(name, "node-a");
HelloCtx::default()
PeerStanding::Free
},
no_deadline(),
)
@@ -118,7 +119,7 @@ fn loopback_hash_mismatch_rejected_with_frame_then_eof() {
let mut wrong = local("node-b");
wrong.build_hash ^= 1;
let responder = std::thread::spawn(move || {
accept_handshake(&mut accepted, wrong, |_| HelloCtx::default(), no_deadline())
accept_handshake(&mut accepted, wrong, |_| PeerStanding::Free, no_deadline())
});
// The dial side receives the reject frame — the compatibility anchor.
match dial_handshake(&mut dialer, &local("node-a"), no_deadline()) {
@@ -142,10 +143,7 @@ fn loopback_tie_break_loser_closed_silently() {
accept_handshake(
&mut accepted,
local("node-a"),
|_| HelloCtx {
name_claimed: false,
dialing_this_peer: true,
},
|_| PeerStanding::Dialing,
no_deadline(),
)
});
@@ -178,7 +176,7 @@ fn loopback_read_ahead_past_hello_survives_into_established_conn() {
let peer = accept_handshake(
&mut accepted,
local("node-b"),
|_| HelloCtx::default(),
|_| PeerStanding::Free,
no_deadline(),
)
.unwrap();
@@ -216,7 +214,7 @@ fn tcp_silent_peer_times_out_on_the_accept_path() {
let r = accept_handshake(
&mut accepted,
local("node-b"),
|_| HelloCtx::default(),
|_| PeerStanding::Free,
Instant::now() + Duration::from_millis(200),
);
let _ = tx.send(r);
@@ -253,7 +251,7 @@ fn tcp_duplicate_name_rejected_by_acceptor() {
.start()
.expect("manager name is free");
let listener = TcpTransport.listen("127.0.0.1:0").unwrap();
let acceptor = spawn_acceptor(listener, local("node-b"));
let acceptor = spawn_acceptor(listener, local("node-b"), Timing::default());
let addr = acceptor.local_addr().to_string();
// First dial offering "dup-node": establishes and registers.
@@ -328,12 +326,12 @@ fn dial_intent_cleared_when_dialer_dies() {
// While the dialer lives, the intent is visible.
match gen_server::call(
MANAGER,
Call::HelloCtx {
Call::Standing {
peer_name: "ghost".into(),
},
) {
Ok(Reply::HelloCtx(ctx)) => assert!(ctx.dialing_this_peer),
other => panic!("HelloCtx failed: {other:?}"),
Ok(Reply::Standing(s)) => assert_eq!(s, PeerStanding::Dialing),
other => panic!("PeerStanding failed: {other:?}"),
}
// Kill it; the monitor must clear the intent without cooperation.
go_tx.send(()).unwrap();
@@ -341,11 +339,11 @@ fn dial_intent_cleared_when_dialer_dies() {
loop {
match gen_server::call(
MANAGER,
Call::HelloCtx {
Call::Standing {
peer_name: "ghost".into(),
},
) {
Ok(Reply::HelloCtx(ctx)) if !ctx.dialing_this_peer => break,
Ok(Reply::Standing(s)) if s != PeerStanding::Dialing => break,
_ if Instant::now() > deadline => {
panic!("dial intent not cleared after dialer death")
}
@@ -372,7 +370,7 @@ fn role_hs_listener() {
.start()
.expect("manager name is free");
let listener = TcpTransport.listen("127.0.0.1:0").unwrap();
let acceptor = spawn_acceptor(listener, local("node-b"));
let acceptor = spawn_acceptor(listener, local("node-b"), Timing::default());
println!("LISTENING {}", acceptor.local_addr());
wait_peers(&["node-a"]);
println!("PEERS node-a");
@@ -389,7 +387,13 @@ fn role_hs_dialer() {
.expect("manager name is free");
let (tx, rx) = mpsc::channel();
smarm::spawn(move || {
let r = dial(&TcpTransport, &addr, "node-b", &local("node-a"));
let r = dial(
&TcpTransport,
&addr,
"node-b",
&local("node-a"),
Timing::default(),
);
let _ = tx.send(r);
});
if let Err(e) = poll_recv(&rx, "dial outcome") {
@@ -459,7 +463,13 @@ fn concurrent_dial_to_same_name_refused() {
// (the addr is unroutable on purpose — it must never be dialed).
let (tx, rx) = mpsc::channel();
smarm::spawn(move || {
let r = dial(&TcpTransport, "127.0.0.1:1", "node-x", &local("node-a"));
let r = dial(
&TcpTransport,
"127.0.0.1:1",
"node-x",
&local("node-a"),
Timing::default(),
);
let _ = tx.send(r);
});
match poll_recv(&rx, "second dial outcome") {