feat(ha-prevote): implement pure voter-side PreVote decision (task 2.1)
raft_on_pre_vote_request: deny on stale term, else reuse on_vote_eligible (primary/heartbeat-stickiness/catch-up gates). Never mutates term/voted_for/ role and never persists or adopts a higher term. 6 RED-first raft unit tests assert the purity invariant; libxce suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
449b8e9e93
commit
722fee5323
3 changed files with 249 additions and 1 deletions
|
|
@ -190,6 +190,45 @@ int raft_on_vote_request(raft_state_t *s, int from_idx, uint64_t term,
|
|||
return RAFT_GRANT;
|
||||
}
|
||||
|
||||
int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term)
|
||||
{
|
||||
/* PreVote is a side-effect-free straw poll: it answers "would you vote for
|
||||
* me in cand_term?" WITHOUT changing any persistent state. Unlike
|
||||
* raft_on_vote_request, this function never adopts cand_term (even when
|
||||
* higher — that adoption is the very thing PreVote exists to prevent), never
|
||||
* touches term / voted_for / voted_for_term / role, and never calls
|
||||
* on_persist. The dispatcher applies the freshness floor before this call,
|
||||
* exactly as on the real-vote path. */
|
||||
|
||||
/* Step 1: stale term — deny (mirrors raft_on_vote_request Step 1). A
|
||||
* candidate proposing a term we already passed could never win a real
|
||||
* election, so it cannot win a PreVote either. No term adoption: even when
|
||||
* cand_term > s->term we only evaluate, never adopt. */
|
||||
if (cand_term < s->term) {
|
||||
if (s->cbs->on_trace)
|
||||
s->cbs->on_trace(s->ctx, "pre_vote_denied_stale_term", NULL);
|
||||
return RAFT_DENY;
|
||||
}
|
||||
|
||||
/* Step 2: application-level eligibility gate — reuse the EXISTING
|
||||
* on_vote_eligible callback (primary-deny / fresh-heartbeat stickiness /
|
||||
* catch-up deny), identical to raft_on_vote_request Step 5. If NULL, every
|
||||
* Raft-eligible candidate is granted. */
|
||||
if (s->cbs->on_vote_eligible != NULL) {
|
||||
if (s->cbs->on_vote_eligible(s->ctx, from_idx, cand_term) == 0) {
|
||||
if (s->cbs->on_trace)
|
||||
s->cbs->on_trace(s->ctx, "pre_vote_denied_eligibility", NULL);
|
||||
return RAFT_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
/* Grant — record NOTHING. No voted_for write (so a later real vote in this
|
||||
* term is still grantable), no persist, no term/role change. */
|
||||
if (s->cbs->on_trace)
|
||||
s->cbs->on_trace(s->ctx, "pre_vote_granted", NULL);
|
||||
return RAFT_GRANT;
|
||||
}
|
||||
|
||||
int raft_on_vote_reply(raft_state_t *s, int from_idx, int granted, uint64_t term)
|
||||
{
|
||||
(void)from_idx;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue