feat(ha-prevote): add raft-library PreVote types, state, callback seam (task 1.2)
Scaffolding only (no bodies): RAFT_PRE_VOTE_WON/LOST/PENDING outcome codes; pre_vote_in_progress / pre_votes_granted / pre_vote_replies[] tally fields appended to raft_state_t; optional on_send_pre_vote_request callback. Adds RAFT_MAX_PEERS=8 to bound per-peer dedup (production cap MAX_HA_CANDIDATES=4). Existing raft election state/layout unchanged; libxce test green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0d660264f5
commit
449b8e9e93
2 changed files with 76 additions and 3 deletions
|
|
@ -12,6 +12,11 @@ typedef enum {
|
|||
RAFT_LEADER = 2,
|
||||
} raft_role_t;
|
||||
|
||||
/* Upper bound on cluster size for the fixed-size pre-vote reply-tracking
|
||||
* array. Matches the simulator bound (RAFT_SIM_MAX_NODES); real clusters
|
||||
* are far smaller. */
|
||||
#define RAFT_MAX_PEERS 8
|
||||
|
||||
/* Forward declaration — full definition follows raft_state_t */
|
||||
typedef struct raft_callbacks_t raft_callbacks_t;
|
||||
|
||||
|
|
@ -32,6 +37,13 @@ typedef struct {
|
|||
int election_decided; /* 1 once on_become_leader/follower fired */
|
||||
uint32_t election_timeout_ms;
|
||||
int my_priority; /* priority index for jitter (0 = highest) */
|
||||
/* ---- PreVote round tally (Req 1.2) ----
|
||||
* Self-contained pre-vote bookkeeping. A pre-vote round mutates NONE of
|
||||
* the persistent Raft state above (term, voted_for, role); these fields
|
||||
* only track the in-flight straw poll and are reset at each round start. */
|
||||
int pre_vote_in_progress; /* 1 while a pre-vote round is active */
|
||||
int pre_votes_granted; /* grants collected this round (incl. self) */
|
||||
int pre_vote_replies[RAFT_MAX_PEERS]; /* per-peer replied flag; avoids double-count */
|
||||
/* set by raft_init — needed by the state machine to fire callbacks */
|
||||
const raft_callbacks_t *cbs;
|
||||
void *ctx;
|
||||
|
|
@ -60,14 +72,23 @@ typedef struct {
|
|||
* on_start_election_after_delay
|
||||
*
|
||||
* OPTIONAL callbacks (may be NULL):
|
||||
* on_vote_eligible — if NULL, all Raft-eligible candidates are granted
|
||||
* on_trace — if NULL, tracing is suppressed
|
||||
* on_vote_eligible — if NULL, all Raft-eligible candidates are granted
|
||||
* on_send_pre_vote_request — if NULL, pre-vote emits are suppressed
|
||||
* on_trace — if NULL, tracing is suppressed
|
||||
*/
|
||||
typedef struct raft_callbacks_t {
|
||||
/* REQUIRED: Send a RequestVote RPC to peer peer_idx for the given term.
|
||||
* The caller encodes config_tag, log_lsn, and any other fields in the wire RPC. */
|
||||
int (*on_send_vote_request)(void *ctx, int peer_idx, uint64_t term);
|
||||
|
||||
/* OPTIONAL: Send a PreVote (straw-poll) request to peer peer_idx for the
|
||||
* prospective term (current term + 1). Unlike on_send_vote_request, this
|
||||
* MUST NOT cause the peer to persist or mutate term/voted_for — it is a
|
||||
* non-binding probe. If NULL, the raft library suppresses the emit and
|
||||
* the caller is responsible for any pre-vote transport itself. */
|
||||
int (*on_send_pre_vote_request)(void *ctx, int peer_idx,
|
||||
uint64_t prospective_term);
|
||||
|
||||
/* OPTIONAL: Application-specific vote eligibility gate.
|
||||
* Called after Raft invariants pass (correct term, voted_for unset).
|
||||
* Returns 1 to grant, 0 to deny. Receives the candidate's peer_idx and
|
||||
|
|
@ -136,6 +157,13 @@ typedef struct {
|
|||
#define RAFT_SKIP_ELECTION 5
|
||||
#define RAFT_ELECTION_STARTED 6
|
||||
|
||||
/* PreVote (straw-poll) outcomes returned by raft_on_pre_vote_reply.
|
||||
* Distinct values from the election codes above; a pre-vote round mutates
|
||||
* no persistent state, so these never imply a term/role transition. */
|
||||
#define RAFT_PRE_VOTE_WON 7 /* quorum of grants reached this round */
|
||||
#define RAFT_PRE_VOTE_LOST 8 /* all peers replied, no quorum */
|
||||
#define RAFT_PRE_VOTE_PENDING 9 /* still awaiting replies */
|
||||
|
||||
/*
|
||||
* raft_init: initialize raft_state_t from a raft_config_t.
|
||||
* on_vote_eligible and on_trace may be NULL (optional policy / observability).
|
||||
|
|
@ -189,6 +217,47 @@ void raft_on_leader_lost(raft_state_t *s);
|
|||
*/
|
||||
int raft_check_and_start_election(raft_state_t *s);
|
||||
|
||||
/* ---------- PreVote (straw poll before a binding election) ----------
|
||||
*
|
||||
* The pre-vote round is a non-binding probe: it asks peers whether they WOULD
|
||||
* grant a vote at term+1, without anyone incrementing term, setting voted_for,
|
||||
* or persisting. Only on RAFT_PRE_VOTE_WON does the caller proceed to the
|
||||
* unchanged raft_start_election (which performs the real term bump + persist).
|
||||
*
|
||||
* INVARIANT (Req 1.2, 6.2): none of these three functions mutate s->term,
|
||||
* s->voted_for, s->voted_for_term, or s->role, and none invoke on_persist.
|
||||
*/
|
||||
|
||||
/*
|
||||
* raft_begin_pre_vote: start a pre-vote round.
|
||||
* Computes prospective term = term + 1 (NOT assigned to s->term), sets
|
||||
* pre_vote_in_progress, pre_votes_granted = 1 (self), and fires
|
||||
* on_send_pre_vote_request(ctx, peer_idx, prospective_term) for each peer
|
||||
* (NULL-checked — suppressed if the callback is unset).
|
||||
* Precondition: role == RAFT_FOLLOWER.
|
||||
*/
|
||||
int raft_begin_pre_vote(raft_state_t *s);
|
||||
|
||||
/*
|
||||
* raft_on_pre_vote_request: answer an incoming PreVote probe.
|
||||
* Denies if cand_term < s->term; otherwise returns on_vote_eligible(ctx,
|
||||
* from_idx, cand_term) (granting when the gate is NULL). Never touches term,
|
||||
* voted_for, role, or fires on_persist. Freshness is applied by the
|
||||
* dispatcher before this call (same contract as the real vote path).
|
||||
* Returns 1 = grant, 0 = deny.
|
||||
*/
|
||||
int raft_on_pre_vote_request(raft_state_t *s, int from_idx, uint64_t cand_term);
|
||||
|
||||
/*
|
||||
* raft_on_pre_vote_reply: tally a reply to our PreVote.
|
||||
* Owns the pre-vote tally in raft_state_t (pre_votes_granted,
|
||||
* pre_vote_replies), mirroring raft_on_vote_reply but WITHOUT firing
|
||||
* on_become_leader/on_become_follower or making any role/term change.
|
||||
* Returns RAFT_PRE_VOTE_WON at quorum, RAFT_PRE_VOTE_LOST when all peers
|
||||
* replied without quorum, else RAFT_PRE_VOTE_PENDING.
|
||||
*/
|
||||
int raft_on_pre_vote_reply(raft_state_t *s, int peer_idx, int granted);
|
||||
|
||||
/* Fills snap with current state. Caller must hold lock. */
|
||||
void raft_snapshot(const raft_state_t *s, raft_snapshot_t *snap);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue