diff options
| author | tv <tv@krebsco.de> | 2026-11-01 19:45:40 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-11-01 19:45:40 +0100 |
| commit | 121d1db3a8c07d3eb450b95e76d1d7eb95d801d3 (patch) | |
| tree | a101b7751255e997cd9750bdb71a88b7bbccfd58 /src/remake/records.rs | |
| parent | 1f3be9d059876f7f3994056b1ac8ce307628a52a (diff) | |
remake: decouple grace time from node's keepalive
Diffstat (limited to 'src/remake/records.rs')
| -rw-r--r-- | src/remake/records.rs | 23 |
1 files changed, 21 insertions, 2 deletions
diff --git a/src/remake/records.rs b/src/remake/records.rs index 55fa8a0..0c4c444 100644 --- a/src/remake/records.rs +++ b/src/remake/records.rs @@ -122,6 +122,11 @@ pub(crate) struct Proposal { /// interval if absent. #[serde(default, skip_serializing_if = "Option::is_none")] pub(crate) switch_delay_ms: Option<u64>, + /// The coordinator's grace period (ms, its `dormant_after`): every node judges this + /// remake's expiry, and when others may abort it, by it, whatever its own keepalive + /// (`Record::grace`). Absent in records from before: each node's own. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub(crate) grace_ms: Option<u64>, /// Members whose votes count (sorted), fixed at proposal time (`vote`). pub(crate) electorate: Vec<String>, /// Votes needed to commit: a majority of the electorate. @@ -311,14 +316,23 @@ impl Record { }) } - pub(crate) fn phase(&self, now: UnixMillis, grace_ms: u64) -> Phase { + /// The phase at `now`, expiry judged by the record's grace period (`grace`; `own`, + /// this node's, for a record without one). + pub(crate) fn phase(&self, now: UnixMillis, own: u64) -> Phase { match &self.decision { Some(Decision::Committed { .. }) => Phase::Committed, Some(Decision::Aborted { .. }) => Phase::Aborted, - None if now > self.proposal.settle_until.plus_ms(grace_ms) => Phase::Expired, + None if now > self.proposal.settle_until.plus_ms(self.grace(own)) => Phase::Expired, None => Phase::Proposed, } } + + /// The grace period this remake is judged by (ms): its coordinator's, the same on + /// every node, so nodes with different keepalives agree on when it expired; `own` + /// for a record from before it was written down. + pub(crate) fn grace(&self, own: u64) -> u64 { + self.proposal.grace_ms.unwrap_or(own) + } } // --- document access --------------------------------------------------------- @@ -564,6 +578,7 @@ mod tests { proposed_at: UnixMillis(settle_until.saturating_sub(1_000)), settle_until: UnixMillis(settle_until), switch_delay_ms: None, + grace_ms: None, electorate: vec!["node-a".into(), "node-b".into()], quorum: 2, kind: DEFAULT_KIND.to_owned(), @@ -654,6 +669,10 @@ mod tests { assert_eq!(record.phase(UnixMillis(10_000), 500), Phase::Proposed); assert_eq!(record.phase(UnixMillis(10_500), 500), Phase::Proposed); assert_eq!(record.phase(UnixMillis(10_501), 500), Phase::Expired); + // The coordinator's grace period, written down, wins over this node's. + record.proposal.grace_ms = Some(2_000); + assert_eq!(record.phase(UnixMillis(10_501), 500), Phase::Proposed); + assert_eq!(record.phase(UnixMillis(12_001), 500), Phase::Expired); record.decision = Some(Decision::Aborted { reason: "test".into(), decided_at: UnixMillis(1), |
