feat(ha-prevote): implement pure candidate-side PreVote start (task 2.2)

raft_begin_pre_vote: compute prospective term (term+1) locally without
persisting, arm the pre_vote_* tally with a self-grant, NULL-safely probe each
non-self peer, return RAFT_PRE_VOTE_WON for single-node else RAFT_OK. Stays
FOLLOWER, schedules no timeout, mutates no persistent state. 6 RED-first tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-06-16 04:33:53 +00:00
parent 722fee5323
commit 6db912b2a9
3 changed files with 254 additions and 1 deletions

View file

@ -229,6 +229,45 @@ int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term)
return RAFT_GRANT;
}
int raft_begin_pre_vote(raft_state_t *s)
{
/* Pre-image of raft_start_election: probe peers + tally a self-grant, but
* STRIP every persistent side effect. Per the header INVARIANT (Req 1.2,
* 1.3, 6.2) this function MUST NOT touch s->term, s->voted_for,
* s->voted_for_term, or s->role, and MUST NOT call on_persist. The node
* stays FOLLOWER throughout the round; CANDIDATE happens later in the
* unchanged raft_start_election once the PreVote round is WON. */
/* Prospective term is a LOCAL value only — never assigned to s->term and
* never persisted (this is the whole point of PreVote). */
uint64_t prospective_term = s->term + 1;
/* Reset and arm the round tally on the raft state. Self always grants. */
s->pre_vote_in_progress = 1;
s->pre_votes_granted = 1; /* self-grant */
memset(s->pre_vote_replies, 0, sizeof(s->pre_vote_replies));
if (s->cbs->on_trace)
s->cbs->on_trace(s->ctx, "pre_vote_start", NULL);
/* Single-node: no peers, the self-grant is already a majority. Win
* immediately and let the caller proceed to raft_start_election. */
if (s->n_peers == 1)
return RAFT_PRE_VOTE_WON;
/* Probe every peer except self with the prospective term (NULL-checked:
* if the callback is unset, the caller owns the transport). */
if (s->cbs->on_send_pre_vote_request) {
for (int i = 0; i < s->n_peers; i++) {
if (i == s->self_idx)
continue;
s->cbs->on_send_pre_vote_request(s->ctx, i, prospective_term);
}
}
return RAFT_OK;
}
int raft_on_vote_reply(raft_state_t *s, int from_idx, int granted, uint64_t term)
{
(void)from_idx;