diff options
| -rw-r--r-- | docs/peers.md | 4 | ||||
| -rw-r--r-- | src/swarm/peer/keepalive.rs | 2 | ||||
| -rw-r--r-- | src/swarm/relay.rs | 35 |
3 files changed, 36 insertions, 5 deletions
diff --git a/docs/peers.md b/docs/peers.md index 8b58df0..8a3333d 100644 --- a/docs/peers.md +++ b/docs/peers.md @@ -287,7 +287,9 @@ writes go on and newer changes are still on their way to it; nor is a neighbour own announcement or STATUS brought them, are this node's: announcements wait 200 to 400 ms, at random, so of two neighbours with the same news one is usually first, and the other skips it), and a neighbour that lacks those heads pulls them (it sets them as the peer's heads in its sync state, so Automerge asks -for what's missing). It pulls from one neighbour at a time (15 s timeout); neighbours +for what's missing), if it still lacks them after a round trip to the announcer (at +most 1 s): while writes go on, the newest are usually on their way to it along a +writer's tree, and would otherwise come twice. It pulls from one neighbour at a time (15 s timeout); neighbours that announce heads it lacks meanwhile wait their turn and are pulled from as soon as the current pull is answered, if they still have something it lacks. The regular STATUS (every third keepalive) triggers the same pull, so gaps close within a few diff --git a/src/swarm/peer/keepalive.rs b/src/swarm/peer/keepalive.rs index f4e961f..0f34eb8 100644 --- a/src/swarm/peer/keepalive.rs +++ b/src/swarm/peer/keepalive.rs @@ -239,7 +239,7 @@ pub(super) async fn note_status(state: &AppState, peer: &str, remote: &transport { let (state, peer, remote) = (state.clone(), peer.to_owned(), remote.clone()); tokio::spawn(async move { - crate::swarm::relay::pull_if_behind(&state, &peer, &remote).await; + crate::swarm::relay::pull_after_grace(&state, &peer, &remote).await; }); } if became_ready { diff --git a/src/swarm/relay.rs b/src/swarm/relay.rs index 99c1381..95abab8 100644 --- a/src/swarm/relay.rs +++ b/src/swarm/relay.rs @@ -6,7 +6,9 @@ //! neighbour, a node *announces* //! its new heads to its other neighbours with a STATUS (heads, about 100 bytes, not the //! changes; no keepalive, and answered only by a neighbour that lacks some of them). A -//! neighbour that sees heads it lacks *pulls*: it enters the +//! neighbour that sees heads it lacks *pulls* (those it still lacks after a round trip +//! to the announcer: while writes go on, the newest are usually on their way to it along +//! a tree, `pull_after_grace`): it enters the //! announced heads as the peer's heads in its sync state, Automerge asks for what's //! missing (`need`), and the announcer answers with exactly those changes. Regular //! keepalives work the same way, so any gap is noticed within a keepalive interval. @@ -51,6 +53,11 @@ use crate::transport::{self, HeadsSummary}; /// A pull that brought nothing for this long no longer blocks pulling from another peer. const PULL_TIMEOUT: Duration = Duration::from_secs(15); +/// The longest a STATUS's heads we lack wait before we pull them (`pull_after_grace`: a +/// round trip to its sender, at most this): over a slow transport (Tor), a change that +/// was missed waits no longer. +const PULL_GRACE_MAX: Duration = Duration::from_secs(1); + /// How long an announcement waits for further changes to ride along, and a random part /// on top (`ANNOUNCE_JITTER`), so that of two neighbours with the same news one usually /// announces first and the other, seeing the first's heads are its own, skips it @@ -224,8 +231,30 @@ pub(crate) async fn lacks(state: &AppState, heads: &HeadsSummary) -> bool { !crate::document::changes::missing(&replica.doc, heads.listed()).is_empty() } -/// A keepalive from `peer`: if it has heads we lack, pull them (one peer at a time; a -/// busy slot queues the peer). Returns whether a pull started. +/// A STATUS from `peer` (a keepalive's, or an announcement): if it has heads we lack, +/// pull them, but only those we still lack after a round trip to it (`PULL_GRACE_MAX` at +/// most; at once if we know none). While writes go on, the newest of them are usually +/// on their way to us along their writers' trees, as fast as the STATUS: they come +/// meanwhile, and pulling them would only fetch them twice. What is still missing then +/// was missed. +pub(crate) async fn pull_after_grace(state: &AppState, peer: &str, remote: &transport::Status) { + if HeadsSummary::of(state.stamp.borrow().heads.to_vec()) == remote.heads { + return; + } + let grace = state + .lock_rtt() + .effective(peer, Instant::now(), state.timing.rtt_fresh_for()) + .map(|rtt| rtt.min(PULL_GRACE_MAX)); + if let Some(grace) = grace + && lacks(state, &remote.heads).await + { + tokio::time::sleep(grace).await; + } + pull_if_behind(state, peer, remote).await; +} + +/// If `peer` has heads we lack (as its STATUS `remote` says), pull them (one peer at a +/// time; a busy slot queues the peer). Returns whether a pull started. pub(crate) async fn pull_if_behind( state: &AppState, peer: &str, |
