fix(ha): correct real-vote reply index mapping for middle-priority nodes (bug-1417)
vote_reply_dispatch passed the compressed, self-excluded local votes_idx (vctx->peer_idx, set by raft_cb_send_vote_request's absolute->local remap) straight through to raft_on_vote_reply, which indexes vote_replies[] in ABSOLUTE cluster space -- the same space raft_start_election's self-mark and per-peer send loop use. For a 3-node cluster's middle node (self_idx=1), the peer at absolute index 2 compresses to votes_idx=1, colliding with vote_replies[self_idx=1] -- the slot raft_start_election already marked for the self-vote. The library's per-peer dedup guard then silently drops the genuine reply as a duplicate, so votes_granted never reaches quorum even though the peer granted. Peer 0 is unaffected (compressed index equals absolute), which is why sequential elections and prior soak runs never surfaced this -- only a middle-priority node losing its higher-priority peer and depending on its lower peer's vote hits the collision. Fixed by routing through ha_repl_ack_abs_peer_idx (already existed, already unit-tested, already used for the analogous heartbeat-ACK path) before calling raft_on_vote_reply. ha_record_vote_reply immediately above is unchanged -- it correctly wants the compressed index for ctx->votes[]. Confirmed pre_vote_reply_dispatch needs no equivalent fix: its send path already stores the absolute index. New regression test drives a real 3-node election end-to-end through the actual decode/dispatch path (not the test-hook bypass every other real-vote test in this file uses): Expected 2 Was 1 pre-fix, GREEN post-fix. Full 79-group suite: zero new failures. 10-round --chaotic HA soak: zero committed divergence, zero committed job loss.
This commit is contained in:
parent
48b4dba700
commit
d5122752fe
3 changed files with 163 additions and 2 deletions
|
|
@ -5230,7 +5230,24 @@ static void vote_reply_dispatch(ha_state_t *ha, ha_elect_ctx_t *ctx, int idx,
|
|||
* raft_cb_send_vote_request via the absolute→local mapping), which is exactly
|
||||
* the index ha_compute_safe_committed_lsn and the test hook use. */
|
||||
ha_record_vote_reply(ha, ctx, idx, term, vote_granted, peer_tag, peer_lsn);
|
||||
raft_on_vote_reply(&ha->raft, idx, vote_granted, term);
|
||||
|
||||
/* bug-1417: raft_on_vote_reply's from_idx must be the ABSOLUTE cluster
|
||||
* index (the space vote_replies[]/self_idx live in — raft_start_election
|
||||
* pre-marks vote_replies[self_idx] and sends to every absolute peer !=
|
||||
* self_idx, raft.c:113-153), NOT the compressed self-excluded votes_idx
|
||||
* `idx` carries for ctx->votes[] above. Passing the compressed idx
|
||||
* straight through collides with vote_replies[self_idx] whenever a
|
||||
* peer's compressed votes_idx numerically equals self_idx (true for any
|
||||
* peer at or above self_idx in a >2-node cluster, e.g. self_idx=1's
|
||||
* absolute peer 2 compresses to votes_idx=1) — raft_on_vote_reply's
|
||||
* per-peer dedup guard then silently treats the genuine reply as an
|
||||
* already-counted duplicate and drops it, so a middle-priority node can
|
||||
* never reach quorum on that peer's vote alone. ha_repl_ack_abs_peer_idx
|
||||
* (ha.c:3779) already implements the exact inverse of
|
||||
* raft_cb_send_vote_request's votes_idx remap and is unit-tested; reuse
|
||||
* it here (ep_is_vote=1) rather than duplicating the arithmetic. */
|
||||
raft_on_vote_reply(&ha->raft, ha_repl_ack_abs_peer_idx(ha, /*ep_is_vote=*/1, idx),
|
||||
vote_granted, term);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue