From 22b7deb7f6ae8d353af94ef1f02bcccf2779e1c2 Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 29 Oct 2026 01:25:55 +0100 Subject: topology: introduce outbound members --- src/swarm/topology.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'src/swarm/topology.rs') diff --git a/src/swarm/topology.rs b/src/swarm/topology.rs index 1d98637..3731036 100644 --- a/src/swarm/topology.rs +++ b/src/swarm/topology.rs @@ -47,6 +47,10 @@ pub(crate) struct NeighbourView { /// Peers that proved they are of our swarm but aren't in our member list (yet): their /// registration hasn't reached us. They are neighbours too (`refresh`). unlisted: BTreeSet, + /// Members the member list gives no address for on our transports, that reach us + /// anyway: they only connect out (no listen address). They are neighbours too while + /// they say they sync with us (`refresh`). + outbound: BTreeSet, neighbours: BTreeSet, /// Every reachable member is a neighbour. full_mesh: bool, @@ -227,11 +231,36 @@ pub(crate) async fn refresh(state: &AppState) -> Option { unlisted.insert(peer); } } + // A member that lists no address on our transports, but reached us anyway, only + // connects out: it isn't in the graph on our side (it's on none of our transports, as + // far as the list goes), while on its side we are, so it would sync with us while we + // never sent it our changes (only answers to its). It picks its neighbours; those it + // picked keep it, as it says (STATUS). + let mut outbound = BTreeSet::new(); + for member in ¤t { + if member == state.local_id() + || unreachable.contains(member) + || transports + .get(member) + .is_some_and(|on| !on.is_disjoint(&local_transports)) + { + continue; + } + if let Some(peer_state) = state.get_peer_state(member).await { + let peer_state = peer_state.lock().await; + if matches!(peer_state.standing, crate::state::Standing::Member { .. }) + && peer_state.syncs_with_us != Some(false) + { + outbound.insert(member.clone()); + } + } + } let previous = { let view = state.neighbours.read_or_recover(); if view.as_ref().is_some_and(|view| { view.members == current && view.unlisted == unlisted + && view.outbound == outbound && view.k == k && view.unreachable == unreachable && view.stable == stable @@ -256,6 +285,7 @@ pub(crate) async fn refresh(state: &AppState) -> Option { &|m| unreachable.contains(m), &|m| stable.contains(m), ); + computed.extend(outbound.iter().cloned()); let listed_neighbours = computed.len(); computed.extend(unlisted.iter().cloned()); let change = NeighbourChange { @@ -277,6 +307,7 @@ pub(crate) async fn refresh(state: &AppState) -> Option { k, neighbours = computed.len(), unlisted = unlisted.len(), + outbound = outbound.len(), added = change.added.len(), removed = change.removed.len(), "Neighbours computed from the member list" @@ -294,6 +325,7 @@ pub(crate) async fn refresh(state: &AppState) -> Option { transports, local_transports, unlisted, + outbound, neighbours: computed, }); Some(change) -- cgit v1.3.1