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.
116 lines
4.0 KiB
Rust
116 lines
4.0 KiB
Rust
//! 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"
|
||
);
|
||
}
|