Files
smarm/tests/cluster_dial_mismatch.rs
claude-asm-audit 4c0e42152f 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.
2026-08-18 16:00:00 +02:00

116 lines
4.0 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! RFC 010 — a seed whose address answers as a *different* name
//! (`DialError::PeerNameMismatch`) is dialed once and then parked: the
//! connector must not redial it on backoff forever.
//!
//! Observed from the misdialed peer: each such dial establishes at the
//! responder (it registers, `node_up`), then the dialer closes on the name
//! check (`node_down`) — one membership blip per attempt. Cross-process: a
//! *server* named `server` subscribes and reports; a *client* on fast
//! timing (50–500ms backoff) seeds `("wrongname", server_addr)`. After the
//! first blip the server counts further `NodeUp`s across 2s — several
//! backoff periods. Parked ⇒ zero. Negative-control-verified: with the park
//! stubbed out the count is ≥ 1 in the same window.
#![cfg(feature = "cluster")]
mod common;
use common::{maybe_child, spawn_node};
use smarm::cluster::envelope::NodeMeta;
use smarm::cluster::membership::{subscribe, NodeEvent};
use smarm::cluster::{start, Config, StaticSeeds, Timing};
use std::time::{Duration, Instant};
const ROLES: &[(&str, fn())] = &[("server", role_server), ("client", role_client)];
fn meta() -> NodeMeta {
NodeMeta {
role: "mismatch".into(),
region: "local".into(),
}
}
fn timing() -> Timing {
Timing {
initial_backoff: Duration::from_millis(50),
max_backoff: Duration::from_millis(500),
..Timing::default()
}
}
fn role_server() {
smarm::run(|| {
let cluster = start(Config {
node_name: "server".into(),
meta: meta(),
listen_addr: std::env::var("SMARM_LISTEN_ADDR")
.unwrap_or_else(|_| "127.0.0.1:0".into()),
strategy: Box::new(StaticSeeds::new(Vec::<(String, String)>::new())),
timing: timing(),
})
.expect("binds");
let ev = subscribe().unwrap();
println!("LISTENING {}", cluster.local_addr());
// First blip: the misdialed client establishes, then closes on us.
loop {
match ev.rx.recv() {
Ok(NodeEvent::NodeDown(i)) if i.name == "client" => break,
Ok(_) => continue,
Err(_) => panic!("manager gone"),
}
}
println!("BLIP");
// Now count further NodeUps across several backoff periods.
let mut more = 0usize;
let t0 = Instant::now();
while t0.elapsed() < Duration::from_millis(2000) {
match ev.rx.try_recv() {
Ok(Some(NodeEvent::NodeUp(i))) if i.name == "client" => more += 1,
Ok(_) => {}
Err(_) => panic!("manager gone"),
}
smarm::sleep(Duration::from_millis(50));
}
println!("MORE {more}");
loop {
smarm::sleep(Duration::from_secs(3600));
}
});
}
fn role_client() {
let server_addr = std::env::var("SMARM_SERVER_ADDR").expect("SMARM_SERVER_ADDR");
smarm::run(move || {
let _cluster = start(Config {
node_name: "client".into(),
meta: meta(),
listen_addr: "127.0.0.1:0".into(),
strategy: Box::new(StaticSeeds::new(vec![(
"wrongname".to_string(),
server_addr,
)])),
timing: timing(),
})
.expect("binds");
println!("CLIENT UP");
loop {
smarm::sleep(Duration::from_secs(3600));
}
});
}
#[test]
fn mismatched_seed_is_dialed_once_then_parked() {
maybe_child(ROLES);
let mut server = spawn_node("server", &[]);
let saddr = server.wait_listening();
let mut client = spawn_node("client", &[("SMARM_SERVER_ADDR", &saddr)]);
client.wait_line("CLIENT UP", |l| l == "CLIENT UP");
server.wait_line("BLIP", |l| l == "BLIP");
let line = server.wait_line("MORE", |l| l.starts_with("MORE "));
let more: usize = line.split_whitespace().nth(1).unwrap().parse().unwrap();
assert_eq!(
more, 0,
"mismatched seed was redialed {more}× after being parked"
);
}