//! 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, out: Sender); } /// 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, impl Into)>) -> Self { StaticSeeds { seeds: seeds .into_iter() .map(|(n, a)| (n.into(), a.into())) .collect(), } } } impl Strategy for StaticSeeds { fn run(self: Box, out: Sender) { for (name, addr) in self.seeds { if out.send(Discovery::Candidate { name, addr }).is_err() { return; // connector gone; nobody to discover for } } } }