summaryrefslogtreecommitdiffstats
path: root/src/state/timing.rs
blob: 6ce5befeb720d8faa89d07daa2be7dd2a3ad81e4 (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
97
//! The intervals the protocol runs on, derived from the keepalive.

use std::time::Duration;

use crate::backoff::Backoff;

/// First wait for a sync answer; generous, since a transport may take a while with large
/// messages (Mycelium sends them in many small packets).
const SYNC_REPLY_TIMEOUT: Duration = Duration::from_secs(10);
/// Upper bound, as a multiple of the first wait: 5 minutes, about as long as a transport
/// tries to deliver a message (`Transport::delivery_limit`).
const SYNC_REPLY_TIMEOUT_MAX_FACTOR: u32 = 30;
/// Upper bound on the wait between probes of an unreachable peer, as a multiple of
/// `dormant_probe`: an hour at the default keepalive (1m). A returning node HELLOs its own
/// neighbours, so this only bounds how long one we never hear from stays out.
const PROBE_MAX_FACTOR: u32 = 6;

#[derive(Clone, Copy, Debug)]
pub(crate) struct Timing {
    pub(crate) keepalive: Duration,
    pub(crate) dormant_after: Duration,
    pub(crate) resync_after: Duration,
    pub(crate) dormant_probe: Duration,
    /// While catching up: send the source a STATUS after this much silence (`swarm::source`).
    pub(crate) catch_up_probe: Duration,
    /// First wait for an answer to a sync message before sending again; doubles while
    /// answers stay out (`SyncSession::reply_backoff`).
    pub(crate) sync_reply_timeout: Duration,
    /// How long the transport tries to deliver a message before dropping it
    /// (`Transport::delivery_limit`): a peer silent for longer has nothing on the way to
    /// us any more.
    pub(crate) delivery_limit: Duration,
}

impl Timing {
    /// How long a round-trip sample stays meaningful (`swarm::rtt`): a few keepalive
    /// intervals, since nodes move.
    pub(crate) fn rtt_fresh_for(&self) -> Duration {
        self.keepalive * 5
    }

    /// The timings for `--keepalive`.
    ///
    /// Each node keeps about `k` neighbours (`--neighbours`) whatever the swarm's size,
    /// and only one side of a pair sends them, so idle, a node sends about `k` messages and
    /// receives about `k` (STATUSes and STATUS_REPLYs) per keepalive interval (more on a stable
    /// member others prefer). The interval trades that against how soon a silent peer is
    /// noticed (`dormant_after`, 4 intervals) and a gap closes (within an interval).
    /// Recommended:
    ///
    /// | Environment | Keepalive | Notes |
    /// |---|---|---|
    /// | Tests | 250 ms – 2 s | dormancy within seconds keeps tests short |
    /// | LAN, servers | 15–30 s | a returning or silent peer is noticed within a minute or two |
    /// | WAN, mobile, metered (the default, 1m) | 60–120 s | about 0.4 messages per second per node at `k` = 12 and 1m |
    ///
    /// `delivery_limit`: the transport's (`Transport::delivery_limit`).
    pub(crate) fn from_keepalive(keepalive: Duration, delivery_limit: Duration) -> Self {
        Self::new(keepalive, SYNC_REPLY_TIMEOUT, delivery_limit)
    }

    /// [`from_keepalive`](Self::from_keepalive) with another first sync reply timeout:
    /// for tests, whose messages don't travel for seconds.
    pub(crate) fn new(
        keepalive: Duration,
        sync_reply_timeout: Duration,
        delivery_limit: Duration,
    ) -> Self {
        Self {
            keepalive,
            dormant_after: keepalive * 4, // dormant_after >= 4* keepalive to tolerate three lost STATUSes in a row
            // >= 2 keepalives, so a second exchange confirms the difference; and >= twice
            // the first sync reply timeout, so a lost message's sender resends it first.
            resync_after: (keepalive * 2).max(sync_reply_timeout * 2),
            dormant_probe: keepalive * 10, // >= 10*: dormant peers cost almost nothing, but still rejoin within minutes
            // Seconds, not a keepalive interval: a dead source should cost little time.
            // Only the source gets these, and only while catching up.
            catch_up_probe: (keepalive / 30).clamp(Duration::from_secs(1), Duration::from_secs(5)),
            sync_reply_timeout,
            delivery_limit,
        }
    }

    /// Waits between probes of an unreachable peer: `dormant_probe`, doubling up to
    /// `PROBE_MAX_FACTOR` times as much.
    pub(crate) fn probe_backoff(&self) -> Backoff {
        Backoff::new(self.dormant_probe, self.dormant_probe * PROBE_MAX_FACTOR)
    }

    /// Waits for sync answers: `sync_reply_timeout`, doubling up to 30 times as much.
    pub(crate) fn sync_reply_backoff(&self) -> Backoff {
        Backoff::new(
            self.sync_reply_timeout,
            self.sync_reply_timeout * SYNC_REPLY_TIMEOUT_MAX_FACTOR,
        )
    }
}