aboutsummaryrefslogtreecommitdiffstats
path: root/src/remake/records.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-01 19:45:40 +0100
committertv <tv@krebsco.de>2026-11-01 19:45:40 +0100
commit78f67ee4dba0fdcd2ad6d91b5d46844284cf2d87 (patch)
tree4ff2d45d5253c491c23beb230544148185392c25 /src/remake/records.rs
parent5640bacf735c6728acf4bc22217b0c07ac4bf7f9 (diff)
remake: decouple grace time from node's keepalive
Diffstat (limited to 'src/remake/records.rs')
-rw-r--r--src/remake/records.rs23
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),