feat(ha-commit-durability): durable-backed vote/PreVote freshness floor + bare-durable candidate advertise (task 4.2)

This commit is contained in:
Rongsong Shen 2026-06-18 09:02:11 +00:00
parent 4b7457144c
commit 3049b620f3
7 changed files with 282 additions and 26 deletions

View file

@ -2141,6 +2141,31 @@ void ha_vote_self_freshness(ha_state_t *ha,
*out_lsn = (local_applied_lsn > learned_lsn) ? local_applied_lsn : learned_lsn;
}
/* ── Candidate advertised LSN (election safety, ha-commit-durability 4.2) ───
*
* A candidate advertises EXACTLY its durable WAL tip on the LSN axis — the
* bare `persist_durable_floor(p)`, NOT the folded
* max(durable_floor, last_known_committed_lsn) that ha_vote_self_freshness
* computes for the VOTER floor.
*
* Why bare, not folded: ha_vote_self_freshness unconditionally returns
* max(local_lsn, last_known_committed_lsn). Feeding it durable_floor would let
* a node restarted with a TRUNCATED log (durable_floor low) but a stale
* volatile learned_committed (high) advertise the stale-high value and WIN —
* exactly the truncated-candidate-wins hole this task closes (design §1a/§5a).
* A candidate may only claim a log position it can durably back.
*
* The candidate's TAG axis is unchanged: it still uses
* max(local_config_tag, learned_tag) (the config epoch is orthogonal to log
* durability — design §1a). Only the LSN axis bypasses the fold.
*
* NULL-safe (persist_durable_floor guards NULL → 0) for the test/seam paths.
*/
uint64_t ha_candidate_advertise_lsn(persist_t *p)
{
return persist_durable_floor(p);
}
/* ── Vote Handler (Req 2.2–2.6, 6.1, 6.2, 6.4, 9.1) ────────────────────────
*
* handle_request_vote: process an incoming OP_requestVote RPC.
@ -3655,14 +3680,19 @@ static void ha_enter_real_election(ha_state_t *ha, cluster_t *c)
if (committed > 0)
persist_truncate_wal_above(&c->persist, committed);
/* Candidate freshness for the RequestVote payload — same floor the voter
* side computes (ha_vote_self_freshness), matching the rdhup path. */
/* Candidate freshness for the RequestVote payload (ha-commit-durability
* task 4.2). The TAG axis uses the same config-epoch fold the voter side
* computes (ha_vote_self_freshness), but the LSN axis advertises the BARE
* durable WAL tip — a candidate may only claim a log position it can
* durably back, so a truncated-restart node cannot over-advertise a stale
* volatile learned-committed value and win (design §1a/§5a). */
uint64_t cand_tag = 0, cand_lsn = 0;
if (c->myconf) {
uint64_t local_tag = c->myconf->config_version_tag;
uint64_t local_lsn = atomic_load_explicit(&ha->applied_lsn,
memory_order_acquire);
ha_vote_self_freshness(ha, local_tag, local_lsn, &cand_tag, &cand_lsn);
ha_vote_self_freshness(ha, local_tag, local_lsn, &cand_tag, NULL);
cand_lsn = ha_candidate_advertise_lsn(&c->persist);
}
int n_peers = (c->myconf) ? c->myconf->peers_cfg.candidates_count : 1;
@ -3738,7 +3768,10 @@ void ha_start_prevote_async(ha_state_t *ha, cluster_t *c)
uint64_t local_tag = c->myconf->config_version_tag;
uint64_t local_lsn = atomic_load_explicit(&ha->applied_lsn,
memory_order_acquire);
ha_vote_self_freshness(ha, local_tag, local_lsn, &cand_tag, &cand_lsn);
/* Task 4.2: TAG via the config-epoch fold, LSN as the bare durable WAL
* tip (PreVote must not over-advertise either — design §1a). */
ha_vote_self_freshness(ha, local_tag, local_lsn, &cand_tag, NULL);
cand_lsn = ha_candidate_advertise_lsn(&c->persist);
n_peers = c->myconf->peers_cfg.candidates_count;
int n_other = (n_peers > 1) ? n_peers - 1 : 0;