aboutsummaryrefslogtreecommitdiffstats
path: root/src/swarm
diff options
context:
space:
mode:
Diffstat (limited to 'src/swarm')
-rw-r--r--src/swarm/relay.rs35
-rw-r--r--src/swarm/sync.rs26
-rw-r--r--src/swarm/tree.rs12
3 files changed, 46 insertions, 27 deletions
diff --git a/src/swarm/relay.rs b/src/swarm/relay.rs
index a8560a5..53c1065 100644
--- a/src/swarm/relay.rs
+++ b/src/swarm/relay.rs
@@ -77,30 +77,33 @@ pub(crate) struct Pending {
}
/// After changes from `from` were applied and our heads changed: passed on at once along
-/// the tree of their `writer` if the message said whose; then announced, in the next
-/// round to those the tree leaves out (to all if there's no tree), and to the others in
-/// the slow round.
-pub(crate) async fn announce(state: &AppState, from: &str, writer: Option<u64>) {
+/// the trees of their `writers` (by their authors' pair keys); then announced, in the
+/// next round to those the trees leave out, and to the others in the slow round. To all
+/// in the next round if any change has no tree to go along: none known whose
+/// (`unauthored`), a writer not in the member list, or we have no part in its tree.
+pub(crate) async fn announce(state: &AppState, from: &str, writers: &[u64], unauthored: bool) {
if !state.initial_sync_done.load(Ordering::Acquire) {
return;
}
- let plan = match writer {
- Some(writer) => crate::swarm::tree::forward(state, writer, from).await,
- None => None,
- };
- let tree = plan.is_some();
+ let mut plans = Vec::new();
+ let mut treeless = unauthored || writers.is_empty();
+ for writer in writers {
+ match crate::swarm::tree::forward(state, *writer, from).await {
+ Some(plan) => plans.push(plan),
+ None => treeless = true,
+ }
+ }
+ let tree = !plans.is_empty();
let schedule = {
let mut pending = state.announce_pending.lock_or_recover();
let schedule = pending.is_none();
let pending = pending.get_or_insert_with(Pending::default);
pending.senders.insert(from.to_owned());
- match &plan {
- Some(plan) => {
- pending.sent.extend(plan.children.iter().cloned());
- pending.targets.extend(plan.uncovered.iter().cloned());
- }
- None => pending.all = true,
+ for plan in &plans {
+ pending.sent.extend(plan.children.iter().cloned());
+ pending.targets.extend(plan.uncovered.iter().cloned());
}
+ pending.all |= treeless;
schedule
};
if schedule {
@@ -115,7 +118,7 @@ pub(crate) async fn announce(state: &AppState, from: &str, writer: Option<u64>)
let slow = slow.get_or_insert_with(Pending::default);
slow.senders.insert(from.to_owned());
slow.all = true;
- if let Some(plan) = &plan {
+ for plan in &plans {
slow.sent.extend(plan.children.iter().cloned());
}
schedule
diff --git a/src/swarm/sync.rs b/src/swarm/sync.rs
index dd5444b..b20f7ef 100644
--- a/src/swarm/sync.rs
+++ b/src/swarm/sync.rs
@@ -376,9 +376,11 @@ pub(crate) async fn handle_sync(
let clock = Arc::clone(&state.clock);
let ahead_ms = state.clock_ahead_ms.load(Ordering::Relaxed);
- // Some((caught_up, heads changed, carried changes)) when applied, None when the
- // message belongs to another epoch.
- tokio::task::spawn_blocking(move || -> Result<Option<(bool, bool, bool)>> {
+ // Some((caught_up, heads changed, carried changes, the new changes' authors and
+ // whether any has none)) when applied, None when the message belongs to another
+ // epoch.
+ type Applied = (bool, bool, bool, (Vec<String>, bool));
+ tokio::task::spawn_blocking(move || -> Result<Option<Applied>> {
// Before the lock: decoding a large message takes a while.
let message = crate::document::sync::decode(&message)?;
let carried = message.carries_changes();
@@ -411,6 +413,13 @@ pub(crate) async fn handle_sync(
let heads_after = replica.heads().to_vec();
stats.heads_seen(heads_after.len());
let changed = heads_after != heads_before;
+ // Whose they are, to pass them on along their writers' trees, however they
+ // came (along a tree, with an answer, pulled).
+ let authors = if changed {
+ crate::document::changes::authors_since(&replica.doc, &heads_before)
+ } else {
+ (Vec::new(), false)
+ };
if changed && measure_propagation {
// Now by the peers' clocks (their median): this node's own clock skew
// doesn't count; the writer's still does.
@@ -432,13 +441,14 @@ pub(crate) async fn handle_sync(
check_caught_up && from_ready && peer_state.sync.have_all_theirs(&replica.doc),
changed,
carried,
+ authors,
)))
})
.await
.context("Sync task panicked")??
};
- let Some((caught_up, changed, carried)) = applied else {
+ let Some((caught_up, changed, carried, (authors, unauthored))) = applied else {
debug!(peer = %peer, epoch = %epoch_label, "Dropped sync message from another epoch");
return Ok(());
};
@@ -460,7 +470,11 @@ pub(crate) async fn handle_sync(
crate::swarm::relay::pulled(state, peer);
// Caught up: our other neighbours may lack what the source had.
if changed || just_caught_up {
- crate::swarm::relay::announce(state, peer, writer.filter(|_| changed)).await;
+ let writers: Vec<u64> = authors
+ .iter()
+ .map(|author| crate::swarm::topology::pair_key(author))
+ .collect();
+ crate::swarm::relay::announce(state, peer, &writers, unauthored).await;
} else if carried
&& let Some(writer) = writer
&& state.initial_sync_done.load(Ordering::Acquire)
@@ -496,7 +510,7 @@ fn fetch_delta(state: &AppState, peer: &str, epoch: transport::Epoch, spec: tran
Ok(changed) => {
state.notify_store_changed();
if changed {
- crate::swarm::relay::announce(&state, &peer, None).await;
+ crate::swarm::relay::announce(&state, &peer, &[], true).await;
}
}
Err(error) => {
diff --git a/src/swarm/tree.rs b/src/swarm/tree.rs
index 8031a69..8093c91 100644
--- a/src/swarm/tree.rs
+++ b/src/swarm/tree.rs
@@ -12,11 +12,13 @@
//! it reaches routes around links that don't work: where a writer behind NAT reaches a
//! single member, that one comes first and the others take it.
//!
-//! A sync message that passes a writer's change on says whose (`SyncEnvelope::writer`).
-//! Whoever applies one passes it on to its own children in that writer's tree
-//! (`forward`), however it got it; so does the writer, to all its neighbours, with its
-//! own writes. A member no member it reaches comes before (or whose parent didn't get
-//! the change) gets it from the announcements (`swarm::relay`), as before.
+//! Whose a change is, its author says (every node writes as itself: its node ID, by
+//! pair key here): whoever applies changes passes them on to its own children in each of
+//! their writers' trees (`forward`), however they came; so does the writer, to all its
+//! neighbours, with its own writes. A sync message along a tree also says whose
+//! (`SyncEnvelope::writer`), for one that had them already (`swarm::sync`). A member no
+//! member it reaches comes before (or whose parent didn't get the change) gets it from
+//! the announcements (`swarm::relay`), as before.
//!
//! The graph is computed once per member list (tens of milliseconds for thousands of
//! members), the distances once per writer and graph (`MAX_WRITERS` kept).