aboutsummaryrefslogtreecommitdiffstats
path: root/src/document
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-07 22:35:22 +0100
committertv <tv@krebsco.de>2026-11-07 22:35:22 +0100
commit91d1bb60cb7a8d92e4e1966dabbcb50a2406c75e (patch)
tree45ea7c8169dfbd7ac14108012254a56961baad1d /src/document
parentcdc81b0f1f0cba1f36d38a8151cad7660074d9f4 (diff)
access: refused once
A refused change is remembered by its hash: offered again it is refused without decoding, judging or logging, so is one built on it, and a peer offering nothing else is answered later each time, up to a minute. Auth plan M6 (re-sending).
Diffstat (limited to 'src/document')
-rw-r--r--src/document/sync.rs23
1 files changed, 23 insertions, 0 deletions
diff --git a/src/document/sync.rs b/src/document/sync.rs
index f124ee8..823d4a3 100644
--- a/src/document/sync.rs
+++ b/src/document/sync.rs
@@ -153,6 +153,10 @@ static NEXT_SESSION: AtomicU64 = AtomicU64::new(1);
/// while it keeps refusing.
const SEND_RETRY: Duration = Duration::from_millis(500);
const SEND_RETRY_MAX: Duration = Duration::from_secs(30);
+/// The first wait before answering a peer offering only changes we refused, and the
+/// longest (`SyncSession::refused_again`).
+const REFUSED_WAIT: Duration = Duration::from_secs(1);
+const REFUSED_WAIT_MAX: Duration = Duration::from_secs(60);
/// One Automerge sync with a peer, from a reset to the next. Everything a reset must
/// forget lives here, so a reset replaces it whole and can't miss a field.
@@ -205,6 +209,10 @@ pub(crate) struct SyncSession {
/// comes late doesn't count: `older_keeps_newer`).
said_heads: Option<Vec<crate::document::ChangeHash>>,
+ /// The wait before answering a peer that offers again only changes we refused
+ /// (`refused_again`).
+ refused_backoff: Backoff,
+
/// The answer we expect to our last sync message(s), if any.
pub(crate) reply: Option<SyncReplyWait>,
/// How long to wait for it; doubles while answers stay out.
@@ -255,6 +263,7 @@ impl SyncSession {
known_elsewhere: std::collections::HashMap::new(),
assumed_lately: std::collections::VecDeque::new(),
said_heads: None,
+ refused_backoff: Backoff::new(REFUSED_WAIT, REFUSED_WAIT_MAX),
reply: None,
reply_backoff,
}
@@ -975,6 +984,20 @@ impl SyncSession {
self.id
}
+ /// The peer offered again only changes we refused (`access::screen`): it would as
+ /// often as we answer, its sync state never learning we won't take them. No message
+ /// to it before the returned wait, which doubles while it goes on.
+ pub(crate) fn refused_again(&mut self, now: Instant) -> Duration {
+ let delay = self.refused_backoff.next();
+ self.retry_at = Some(self.retry_at.map_or(now + delay, |at| at.max(now + delay)));
+ delay
+ }
+
+ /// The peer brought changes we took (or none refused before): answered at once again.
+ pub(crate) fn refusals_settled(&mut self) {
+ self.refused_backoff.reset();
+ }
+
/// Our transport took the message generated from session `session`. Returns whether
/// to generate again: a pass was requested while it was handed over, or a reset
/// replaced the session meanwhile (any pass requested then was lost with the old