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.
77 lines
3.2 KiB
Rust
77 lines
3.2 KiB
Rust
//! RFC 010 c7b — peer discovery: the [`Strategy`] seam and the static-seeds
|
|
//! implementation.
|
|
//!
|
|
//! A strategy is **push-based and runs as its own actor**: the
|
|
//! [`connector`](crate::cluster::connector) spawns it with the sending end of
|
|
//! a channel, and the strategy emits [`Discovery`] events whenever it learns
|
|
//! something — once at startup for a static list, continuously for a future
|
|
//! mDNS/DNS strategy — for as long as it cares to run. Returning ends the
|
|
//! strategy actor; the candidates it pushed live on in the connector (the
|
|
//! connector owns all retry/backoff state, so a strategy never re-announces).
|
|
//!
|
|
//! A candidate is a **`(node_name, addr)` pair**, not a bare address: the
|
|
//! dial path and the D7 tie-break are keyed by peer *name* (the dial intent
|
|
//! must be registered before connecting so a crossing inbound `Hello` sees
|
|
//! it), so an anonymous dial would reintroduce exactly the
|
|
//! simultaneous-connect flap D7 exists to prevent. Discovery mechanisms know
|
|
//! names — that is what they discover.
|
|
|
|
use crate::channel::Sender;
|
|
|
|
/// A discovery event, as pushed by a [`Strategy`].
|
|
///
|
|
/// `Candidate` announces, `Withdrawn` retracts — the primitive pair. A
|
|
/// strategy that wants TTL semantics builds them on top (track its own
|
|
/// last-seen times, emit `Withdrawn` on expiry); the connector deliberately
|
|
/// has no clock of its own for candidates (D11: strategies never
|
|
/// re-announce, the connector owns retry). `#[non_exhaustive]` so more can
|
|
/// land without breaking strategies.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
#[non_exhaustive]
|
|
pub enum Discovery {
|
|
/// A peer worth dialing: its claimed node name and a dialable address.
|
|
Candidate { name: String, addr: String },
|
|
/// Stop dialing this `(name, addr)`. Dial-set only: a connection that
|
|
/// is already up is membership's business and is left alone; an
|
|
/// attempt in flight completes on its own; a later `Candidate` for the
|
|
/// same pair re-adds it with fresh backoff. Unknown pairs are ignored.
|
|
Withdrawn { name: String, addr: String },
|
|
}
|
|
|
|
/// A source of peers to dial. Implementations are spawned as actors by the
|
|
/// connector — see the module docs for the contract.
|
|
pub trait Strategy: Send + 'static {
|
|
/// Run the strategy: push [`Discovery`] events into `out` as they are
|
|
/// learned; return when done discovering (or when `out` reports closed —
|
|
/// the connector is gone). Runs inside an actor, so blocking
|
|
/// cooperatively is fine.
|
|
fn run(self: Box<Self>, out: Sender<Discovery>);
|
|
}
|
|
|
|
/// The static-seeds strategy: a fixed `(name, addr)` list, announced once.
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct StaticSeeds {
|
|
seeds: Vec<(String, String)>,
|
|
}
|
|
|
|
impl StaticSeeds {
|
|
pub fn new(seeds: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>) -> Self {
|
|
StaticSeeds {
|
|
seeds: seeds
|
|
.into_iter()
|
|
.map(|(n, a)| (n.into(), a.into()))
|
|
.collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Strategy for StaticSeeds {
|
|
fn run(self: Box<Self>, out: Sender<Discovery>) {
|
|
for (name, addr) in self.seeds {
|
|
if out.send(Discovery::Candidate { name, addr }).is_err() {
|
|
return; // connector gone; nobody to discover for
|
|
}
|
|
}
|
|
}
|
|
}
|