aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/dashboard.md7
-rw-r--r--src/swarm/links.rs30
2 files changed, 33 insertions, 4 deletions
diff --git a/docs/dashboard.md b/docs/dashboard.md
index d641603..821c9e8 100644
--- a/docs/dashboard.md
+++ b/docs/dashboard.md
@@ -356,9 +356,10 @@ which transport, whether only through a relay, the round trip (as the paths laye
measures it), whether it holds the peer dormant or the path is down, the peer's other
transports with a path up (each with its fastest path's round trip, and whether it goes
only through a relay), and the peer's clock against the member's ([`measurements.md`](measurements.md),
-Per peer, `clock`). A member answers a LINKS from members only, at most every 5 s (one
-asking it to relay, each time), and every build answers, with or without the feature
-(`swarm::links`).
+Per peer, `clock`). A member answers a LINKS from members only, at most every 5 s, and
+asks for each peer that has it relay at most 512 members at once and 32 a second on
+average (the rest of a request is dropped); every build answers, with or without the
+feature (`swarm::links`).
**Clocks.** The clocks in the links chain every member's clock to this node's, along
the narrowest bounds, and the map takes each against their median: a member more than
diff --git a/src/swarm/links.rs b/src/swarm/links.rs
index cdbae67..4b89e94 100644
--- a/src/swarm/links.rs
+++ b/src/swarm/links.rs
@@ -66,6 +66,11 @@ const MAX_RELAYED: usize = 64;
pub(crate) const DEFAULT_BATCH: Duration = Duration::from_millis(250);
const MIN_BATCH: Duration = Duration::from_millis(10);
const MAX_BATCH: Duration = Duration::from_secs(5);
+/// Members a peer may have us ask for it, at most: as many at once (`RELAY_BURST`), and
+/// on average this many per second (`RELAY_RATE`): a dashboard asking every member
+/// (`MAX_ASKED` there, 512) every 30 s needs less. The rest of a request is dropped.
+const RELAY_BURST: f64 = 512.0;
+const RELAY_RATE: f64 = 32.0;
/// Nodes a relayed LINKS goes through at most, the first included.
const MAX_HOPS: u8 = 3;
@@ -97,6 +102,9 @@ struct Inner {
relaying: HashMap<String, HashMap<String, Relaying>>,
/// Relayed links due to an asker, by asker: the members whose links changed.
due: HashMap<String, HashSet<String>>,
+ /// How many members each peer may still have us ask for it (`RELAY_BURST`,
+ /// `RELAY_RATE`), as of when.
+ relay_budget: HashMap<String, (f64, Instant)>,
}
/// One asker's relayed subscription to a member.
@@ -544,7 +552,7 @@ async fn relay(state: &AppState, asker: &str, request: LinksRequest) {
let left = Duration::from_millis(u64::from(request.batch_ms)).min(MAX_BATCH);
let window = (left / (u32::from(request.hops) + 1)).max(MIN_BATCH);
let rest = left.saturating_sub(window);
- let members: Vec<String> = request
+ let mut members: Vec<String> = request
.of
.into_iter()
.filter(|member| member != asker && member != state.local_id())
@@ -552,6 +560,26 @@ async fn relay(state: &AppState, asker: &str, request: LinksRequest) {
.collect();
let known: Vec<String> = {
let mut inner = state.links.inner.lock_or_recover();
+ // Within the asker's budget: each member costs one.
+ if inner.relay_budget.len() >= MAX_KEPT && !inner.relay_budget.contains_key(asker) {
+ inner.relay_budget.retain(|_, (budget, at)| {
+ *budget + now.saturating_duration_since(*at).as_secs_f64() * RELAY_RATE
+ < RELAY_BURST
+ });
+ }
+ let (budget, at) = inner
+ .relay_budget
+ .entry(asker.to_owned())
+ .or_insert((RELAY_BURST, now));
+ *budget = (*budget + now.saturating_duration_since(*at).as_secs_f64() * RELAY_RATE)
+ .min(RELAY_BURST);
+ *at = now;
+ let allowed = (*budget as usize).min(members.len());
+ *budget -= allowed as f64;
+ if allowed < members.len() {
+ debug!(asker = %asker, asked = members.len(), allowed, "Relayed LINKS over its budget; asking for part of it");
+ members.truncate(allowed);
+ }
for member in &members {
if inner.relaying.len() >= MAX_KEPT && !inner.relaying.contains_key(member) {
inner