From 91d1bb60cb7a8d92e4e1966dabbcb50a2406c75e Mon Sep 17 00:00:00 2001 From: tv Date: Sat, 7 Nov 2026 22:35:22 +0100 Subject: 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). --- src/document/sync.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'src/document/sync.rs') 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>, + /// 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, /// 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 -- cgit v1.3.1