aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/swarm/peer/inbound.rs24
-rw-r--r--src/swarm/peer/keepalive.rs4
-rw-r--r--src/swarm/reach.rs8
-rw-r--r--src/swarm/relay.rs8
-rw-r--r--src/transport/mod.rs4
5 files changed, 36 insertions, 12 deletions
diff --git a/src/swarm/peer/inbound.rs b/src/swarm/peer/inbound.rs
index 1454c3e..28066cb 100644
--- a/src/swarm/peer/inbound.rs
+++ b/src/swarm/peer/inbound.rs
@@ -143,13 +143,31 @@ pub(crate) async fn handle_message(
crate::swarm::blob::handle(state, &peer, blob).await;
}
transport::ParsedMessage::Status(remote) => {
- // An announcement is no keepalive; it is answered only if our heads differ,
- // so the announcer learns what we have that it lacks.
+ // An announcement is no keepalive; it is answered only if we lack some of the
+ // heads it announced (`relay::lacks`), so the announcer learns what we have
+ // that it lacks while we pull. Having all of them, we are as far or further,
+ // and what we have further we announced ourselves; differing heads alone are
+ // no reason, as changes on their way to us make them differ while writes go on.
if !remote.announcing {
status_received(state, &peer).await;
}
let local = on_status(state, &peer, &remote, reactivated).await;
- if remote.announcing && local.heads == remote.heads {
+ if remote.announcing {
+ if local.heads != remote.heads {
+ // Off the consumer: telling which heads we lack takes the document's
+ // lock.
+ let (state, peer) = (state.clone(), peer.clone());
+ tokio::spawn(async move {
+ if crate::swarm::relay::lacks(&state, &remote.heads).await
+ && let Err(error) = state
+ .transport
+ .send_message(&peer, &ParsedMessage::StatusReply(local))
+ .await
+ {
+ debug!(peer = %peer, error = %format!("{error:#}"), "STATUS_REPLY failed");
+ }
+ });
+ }
return Ok(());
}
if let Err(error) = state
diff --git a/src/swarm/peer/keepalive.rs b/src/swarm/peer/keepalive.rs
index 8362d73..f4e961f 100644
--- a/src/swarm/peer/keepalive.rs
+++ b/src/swarm/peer/keepalive.rs
@@ -33,8 +33,8 @@ pub(crate) async fn send_status(state: &AppState, peer: &str) -> Result<()> {
}
/// Sends `peer` a STATUS announcing our new heads (`swarm::relay`): no keepalive, so it
-/// leaves the keepalives' schedule as it is, and it is answered only if the peer's heads
-/// differ (`Status::announcing`). Whom we reach only `with_reaches` (`swarm::reach`):
+/// leaves the keepalives' schedule as it is, and it is answered only if the peer lacks
+/// some of our heads (`Status::announcing`). Whom we reach only `with_reaches` (`swarm::reach`):
/// announcements of heads go often.
pub(crate) async fn announce_heads(state: &AppState, peer: &str, with_reaches: bool) -> Result<()> {
let local = local_status(state, peer).await;
diff --git a/src/swarm/reach.rs b/src/swarm/reach.rs
index 7bd0f73..5c89d04 100644
--- a/src/swarm/reach.rs
+++ b/src/swarm/reach.rs
@@ -72,8 +72,8 @@ pub(crate) fn published(state: &AppState) -> Vec<u64> {
/// With the peer worker's periodic pass (every 5 s), and at once when a member we said
/// we reach stops answering (`reached_stops_answering`): whom we reach now; if it changed,
-/// our neighbours are told at once (a STATUS each, unanswered if the heads are the
-/// same). A change that is undone within a pass isn't told.
+/// our neighbours are told at once (a STATUS each, unanswered unless they lack some of
+/// our heads). A change that is undone within a pass isn't told.
pub(crate) async fn check(state: &AppState) {
let now = measure(state).await;
{
@@ -109,8 +109,8 @@ pub(crate) fn reached_stops_answering(state: &AppState, peer: &str) -> bool {
.is_some_and(|keys| keys.contains(&topology::pair_key(peer)))
}
-/// A STATUS to `peer` with whom we reach: as an announcement (unanswered if the heads
-/// are the same), but with `reaches`.
+/// A STATUS to `peer` with whom we reach: as an announcement (unanswered unless it lacks
+/// some of our heads), but with `reaches`.
async fn tell(state: &AppState, peer: &str) -> anyhow::Result<()> {
announce_heads(state, peer, true).await
}
diff --git a/src/swarm/relay.rs b/src/swarm/relay.rs
index 53c1065..99c1381 100644
--- a/src/swarm/relay.rs
+++ b/src/swarm/relay.rs
@@ -5,7 +5,7 @@
//! further by itself but along a writer's tree. So after applying changes from one
//! 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 whose heads differ). A
+//! 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
//! 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
@@ -218,6 +218,12 @@ pub(crate) struct Pulling {
waiting: HashMap<String, transport::Status>,
}
+/// Whether we lack any of `heads` (with a digest, any of the sample it lists).
+pub(crate) async fn lacks(state: &AppState, heads: &HeadsSummary) -> bool {
+ let replica = state.doc.read().await;
+ !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.
pub(crate) async fn pull_if_behind(
diff --git a/src/transport/mod.rs b/src/transport/mod.rs
index e27ce2d..17d6d4c 100644
--- a/src/transport/mod.rs
+++ b/src/transport/mod.rs
@@ -203,8 +203,8 @@ pub(crate) struct Status {
/// recipient holds it dormant at once, rather than after `dormant_after` of silence.
pub(crate) leaving: bool,
/// The sender announces new heads (`swarm::relay`), rather than keeping alive: a
- /// STATUS answered only if the recipient's heads differ. Most announcements reach a
- /// neighbour that has the changes already, which has nothing to tell.
+ /// STATUS answered only if the recipient lacks some of the heads. Most announcements
+ /// reach a neighbour that has the changes already, which has nothing to tell.
pub(crate) announcing: bool,
/// Whom the sender reaches (`swarm::reach`): the pair keys (`topology::pair_key`)
/// of the members it has an active session with that answer, sorted. In keepalive STATUSes and