summaryrefslogtreecommitdiffstats
path: root/src/swarm/peer/epoch.rs
blob: 56711b1df3df2fa79af2f2def97c51de134c8aa1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! The epochs peers advertise: recorded per peer, late messages aside, and compared
//! with ours.

use tokio::time::Instant;
use tracing::{debug, info, warn};

use crate::document::replica::{Epoch, EpochRelation};
use crate::state::AppState;
use crate::swarm::sync::SyncEvent;
use crate::transport;

/// Records the epoch a peer advertised and compares it with ours. Returns true when the
/// message's epoch is ours, the only case in which its documents may be synced.
///
/// `seq` is the peer's `epoch_seq` with it. A message with a lower one than we have was
/// sent before the peer's last epoch change and delivered late: its epoch isn't
/// recorded, so it can't flip our view of the peer back. The count starts over when the
/// peer restarts, which its HELLO or HELLO_REPLY tells by a new run (`run`).
pub(super) async fn observe_peer_epoch(
    state: &AppState,
    peer: &str,
    remote: Epoch,
    seq: u32,
    run: Option<transport::RunId>,
) -> bool {
    let local = state.local_epoch();
    let relation = local.relation_to(&remote);

    let Some(peer_state_mutex) = state.get_peer_state(peer).await else {
        return relation == EpochRelation::Same;
    };

    let previous = {
        let mut peer_state = peer_state_mutex.lock().await;
        if let Some(run) = run.filter(|run| peer_state.run_id != Some(*run)) {
            peer_state.run_id = Some(run);
            peer_state.epoch_seq = None;
        }
        if peer_state.epoch_seq.is_some_and(|newest| seq < newest) {
            debug!(
                peer = %peer,
                epoch = %remote,
                seq,
                newest = ?peer_state.epoch_seq,
                "Ignoring a late message's epoch"
            );
            return relation == EpochRelation::Same;
        }
        peer_state.epoch_seq = Some(seq);
        let previous = peer_state.epoch.replace(remote);
        if previous.is_some_and(|previous| previous != remote) {
            // The peer switched documents; our sync state describes its old one. Count
            // the restart as sync activity, so the keepalive heal doesn't re-handshake
            // (resetting both sides again) while this fresh sync is under way.
            peer_state.reset_sync();
            peer_state.last_sync_rx = Some(Instant::now());
            // Whether it would sync with us was said about its old epoch.
            peer_state.syncs_with_us = None;
        }
        previous
    };

    // Log transitions only, not every keepalive.
    if previous != Some(remote) {
        match relation {
            EpochRelation::Same => {
                if previous.is_some() {
                    info!(peer = %peer, epoch = %remote, "Peer is on our epoch again; resuming sync");
                    let _ = state
                        .sync_events
                        .send(SyncEvent::PeerNeedsSync(peer.to_owned()));
                }
            }
            // Expected after a remake: either side switches or catches up.
            EpochRelation::PeerAhead | EpochRelation::PeerBehind => {
                info!(
                    peer = %peer,
                    local_epoch = %local,
                    peer_epoch = %remote,
                    relation = ?relation,
                    "Peer is on another epoch; not syncing with it until both are on the same"
                );
            }
            EpochRelation::Diverged => {
                warn!(
                    peer = %peer,
                    local_epoch = %local,
                    peer_epoch = %remote,
                    "Peer is on a different epoch of the same generation; not syncing with it"
                );
            }
        }
    }

    relation == EpochRelation::Same
}