summaryrefslogtreecommitdiffstats
path: root/src/state.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/state.rs')
-rw-r--r--src/state.rs65
1 files changed, 59 insertions, 6 deletions
diff --git a/src/state.rs b/src/state.rs
index eaa6562..5e9b03b 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -139,6 +139,9 @@ pub(crate) struct SyncSession {
/// Who sends the first message of this session.
pub(crate) opening: Opening,
+ /// What started this session.
+ pub(crate) cause: SessionCause,
+
/// Consecutive exchanges that changed nothing (livelock detection).
pub(crate) stalled_exchanges: u32,
}
@@ -160,6 +163,49 @@ pub(crate) enum Opening {
Theirs { until: Instant },
}
+/// What started a sync session. Kept for the log: when the peer doesn't open within the
+/// grace period, the cause usually tells why.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub(crate) enum SessionCause {
+ /// First session with a peer we just learned about.
+ NewPeer,
+ /// The peer sent HELLO: it (re)started its handshake with us.
+ PeerHello,
+ /// Its HELLO_REPLY completed our handshake.
+ HandshakeComplete,
+ /// The peer advertised another epoch than before.
+ PeerEpochChanged,
+ /// We switched epochs.
+ OwnEpochSwitch,
+ /// The peer went dormant.
+ PeerDormant,
+ /// The peer failed the swarm key check.
+ KeyRejected,
+}
+
+impl SessionCause {
+ /// The likely reason the peer didn't open a session started this way.
+ pub(crate) fn missing_opening_hint(self) -> &'static str {
+ match self {
+ Self::NewPeer | Self::PeerHello | Self::HandshakeComplete => {
+ "the peer may not count us as a member yet (our HELLO_REPLY hasn't reached it), \
+ or its opening was lost"
+ }
+ Self::PeerEpochChanged => {
+ "the peer switched epochs but doesn't know we're on its new one yet, or its \
+ opening was lost"
+ }
+ Self::OwnEpochSwitch => {
+ "we switched epochs; the peer opens only once it learns that, and it may not \
+ have yet"
+ }
+ Self::PeerDormant | Self::KeyRejected => {
+ "only our side reset, so the peer had no reason to open"
+ }
+ }
+ }
+}
+
/// Sync messages we sent and expect an answer to. Automerge assumes reliable delivery:
/// a lost message leaves both sides waiting (`in_flight`), and changes it carried are
/// never sent again (`sent_hashes`). We resend after a timeout instead.
@@ -181,8 +227,9 @@ const SYNC_REPLY_TIMEOUT_MAX: Duration = Duration::from_secs(300);
const SYNC_OPENING_GRACE: Duration = Duration::from_secs(3);
impl SyncSession {
- pub(crate) fn new(leads: bool) -> Self {
+ pub(crate) fn new(leads: bool, cause: SessionCause) -> Self {
Self {
+ cause,
state: sync::State::new(),
outbound: None,
reply: None,
@@ -212,6 +259,12 @@ impl SyncSession {
}
}
+ /// The peer should have opened by now but hasn't said anything: we open ourselves.
+ pub(crate) fn opening_overdue(&self, now: Instant) -> bool {
+ self.state.their_heads.is_none()
+ && matches!(self.opening, Opening::Theirs { until } if until <= now)
+ }
+
/// A sync message went out; `sent` are the changes it carried for the first time.
pub(crate) fn expect_reply(&mut self, sent: Vec<automerge::ChangeHash>, now: Instant) {
match &mut self.reply {
@@ -258,7 +311,7 @@ impl PeerState {
hello_state: HelloState::default(),
reply_owed: false,
pending_inbound_sync: Vec::new(),
- sync: SyncSession::new(leads),
+ sync: SyncSession::new(leads, SessionCause::NewPeer),
liveness: Liveness::Active,
added_at: Instant::now(),
last_seen: None,
@@ -271,8 +324,8 @@ impl PeerState {
/// Start Automerge sync with this peer from scratch. Any envelope generated against
/// the old state is discarded; a stale RetryDelivery event is then a no-op.
- pub(crate) fn reset_sync(&mut self) {
- self.sync = SyncSession::new(self.leads);
+ pub(crate) fn reset_sync(&mut self, cause: SessionCause) {
+ self.sync = SyncSession::new(self.leads, cause);
}
/// An overdue sync answer from an established, active peer (see
@@ -310,7 +363,7 @@ impl PeerState {
}
self.liveness = Liveness::Dormant { since: now };
- self.reset_sync();
+ self.reset_sync(SessionCause::PeerDormant);
if !matches!(self.hello_state, HelloState::Sending { .. }) {
self.hello_state = HelloState::AwaitingReply {
@@ -977,7 +1030,7 @@ impl Host for AppState {
let now = Instant::now();
for peer in state.peer_states.blocking_read().values() {
let mut peer = peer.blocking_lock();
- peer.reset_sync();
+ peer.reset_sync(SessionCause::OwnEpochSwitch);
// A fresh start, not an idle sync: no heal re-handshake for it.
peer.last_sync_rx = Some(now);
}