fix(ha-bootstrap): priority staggering and candidate_id in election
Two Raft correctness fixes: 1. Priority staggering in ha_wait_for_heartbeat_or_timeout(): Add my_priority × election_priority_step_ms to the timeout so ctrl-a (priority=0) times out first and starts election before ctrl-b (priority=1) and ctrl-c (priority=2). This gives the highest-priority node a head start and prevents simultaneous campaigning that causes perpetual split votes. 2. candidate_id in vote RPC: Pass the actual node name (myconf->name) as candidate_id instead of "". This fixes the voted_for deduplication gate (Req 2.6): a peer that already voted for ctrl-a will correctly deny a second vote request from ctrl-b in the same term. Thread context gets my_name field; am_i_primary_quorum() takes my_name parameter; all call sites updated (myapp.c, ha.c RDHUP, test_ha_election.c stubs).
This commit is contained in:
parent
fcfb82f7c7
commit
80950ffbba
4 changed files with 20 additions and 9 deletions
|
|
@ -636,8 +636,11 @@ ctrl_role_t ha_wait_for_heartbeat_or_timeout(ha_state_t *ha)
|
|||
if (ha->jitter_seed == 0)
|
||||
ha_seed_jitter(&ha->jitter_seed, ha->my_priority);
|
||||
|
||||
/* Compute timeout in milliseconds: election_timeout + random jitter */
|
||||
/* Compute timeout: election_timeout + priority_step * priority + random jitter.
|
||||
* Higher-priority (lower index) nodes time out sooner, giving them a head
|
||||
* start in the election before lower-priority nodes begin campaigning. */
|
||||
long to_ms = (long)ha->config.election_timeout_ms;
|
||||
to_ms += (long)ha->my_priority * (long)ha->config.election_priority_step_ms;
|
||||
if (ha->config.election_jitter_max_ms > 0) {
|
||||
to_ms += (long)(xorshift64_next(&ha->jitter_seed)
|
||||
% ((uint64_t)ha->config.election_jitter_max_ms + 1));
|
||||
|
|
@ -1020,6 +1023,7 @@ typedef struct {
|
|||
uint64_t my_tag; /* config_tag sent in the request */
|
||||
uint64_t my_lsn; /* WAL LSN sent in the request */
|
||||
int timeout_ms; /* Connect + I/O timeout */
|
||||
const char *my_name; /* Candidate node name for voted_for deduplication */
|
||||
} vote_thread_ctx_t;
|
||||
|
||||
/*
|
||||
|
|
@ -1097,7 +1101,7 @@ static void *vote_request_thread(void *arg)
|
|||
request_vote_req_t vote_req;
|
||||
memset(&vote_req, 0, sizeof(vote_req));
|
||||
vote_req.term = vctx->my_term;
|
||||
vote_req.candidate_id = (char *)""; /* anonymous; peer reads configTag/lsn */
|
||||
vote_req.candidate_id = (char *)(vctx->my_name ? vctx->my_name : "");
|
||||
vote_req.config_tag = vctx->my_tag;
|
||||
vote_req.last_log_lsn = vctx->my_lsn;
|
||||
|
||||
|
|
@ -1229,7 +1233,8 @@ ctrl_role_t am_i_primary_quorum(ha_state_t *ha,
|
|||
int n_peers,
|
||||
xce_candidate_t **peers,
|
||||
uint64_t my_config_tag,
|
||||
uint64_t my_lsn)
|
||||
uint64_t my_lsn,
|
||||
const char *my_name)
|
||||
{
|
||||
/* Req 2.9: single-node self-elects immediately */
|
||||
if (n_peers == 1) {
|
||||
|
|
@ -1320,6 +1325,7 @@ ctrl_role_t am_i_primary_quorum(ha_state_t *ha,
|
|||
vctxs[i].my_lsn = my_lsn;
|
||||
vctxs[i].timeout_ms =
|
||||
(int)ha->config.probe_connect_timeout_ms;
|
||||
vctxs[i].my_name = my_name;
|
||||
pthread_create(&tids[i], NULL, vote_request_thread, &vctxs[i]);
|
||||
}
|
||||
|
||||
|
|
@ -1576,7 +1582,8 @@ void ha_on_primary_ctrl_fd_rdhup(ha_state_t *ha, cluster_t *c)
|
|||
uint64_t my_tag = c->myconf->config_version_tag;
|
||||
uint64_t my_lsn = atomic_load_explicit(&c->persist.committed_lsn,
|
||||
memory_order_acquire);
|
||||
ctrl_role_t new_role = am_i_primary_quorum(ha, np, peer_ptrs, my_tag, my_lsn);
|
||||
ctrl_role_t new_role = am_i_primary_quorum(ha, np, peer_ptrs, my_tag, my_lsn,
|
||||
c->myconf ? c->myconf->name : "");
|
||||
if (new_role == CTRL_PRIMARY)
|
||||
INFO("HA: RDHUP recovery → CTRL_PRIMARY (Req 6.8)");
|
||||
else
|
||||
|
|
|
|||
|
|
@ -347,7 +347,8 @@ ctrl_role_t am_i_primary_quorum(ha_state_t *ha,
|
|||
int n_peers,
|
||||
xce_candidate_t **peers,
|
||||
uint64_t my_config_tag,
|
||||
uint64_t my_lsn);
|
||||
uint64_t my_lsn,
|
||||
const char *my_name);
|
||||
|
||||
/*
|
||||
* handle_request_vote_reply: process an incoming reply to an OP_requestVote RPC.
|
||||
|
|
|
|||
|
|
@ -152,7 +152,8 @@ int myapp_run(myconf_t* myconf)
|
|||
uint64_t my_lsn = (uint64_t)atomic_load_explicit(&c.ha.applied_lsn,
|
||||
memory_order_acquire);
|
||||
ctrl_role_t elected = am_i_primary_quorum(
|
||||
&c.ha, n_peers, (n_other > 0) ? peer_ptrs : NULL, my_tag, my_lsn);
|
||||
&c.ha, n_peers, (n_other > 0) ? peer_ptrs : NULL,
|
||||
my_tag, my_lsn, myconf->name);
|
||||
|
||||
if (elected == CTRL_PRIMARY) {
|
||||
final_role = CTRL_PRIMARY;
|
||||
|
|
|
|||
|
|
@ -108,7 +108,8 @@ ctrl_role_t ha_elect_evaluate_and_signal(ha_state_t *ha, ha_elect_ctx_t *ctx);
|
|||
|
||||
ctrl_role_t am_i_primary_quorum(ha_state_t *ha, int n_peers,
|
||||
xce_candidate_t **peers,
|
||||
uint64_t my_config_tag, uint64_t my_lsn);
|
||||
uint64_t my_config_tag, uint64_t my_lsn,
|
||||
const char *my_name);
|
||||
|
||||
void handle_request_vote_reply(ha_state_t *ha, ha_elect_ctx_t *ctx,
|
||||
int peer_idx, uint64_t term,
|
||||
|
|
@ -531,7 +532,8 @@ static void test_single_node_self_elects_as_primary(void)
|
|||
1, /* n_peers = 1 (single node) */
|
||||
NULL, /* no other peers */
|
||||
500ULL, /* my_config_tag */
|
||||
100ULL /* my_lsn */);
|
||||
100ULL, /* my_lsn */
|
||||
"test-node");
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY, (int)role);
|
||||
|
||||
|
|
@ -613,7 +615,7 @@ static void test_split_vote_full_retry_increments_term(void)
|
|||
|
||||
/* Single-node: am_i_primary_quorum must self-elect and bump the term.
|
||||
* (Multi-node split-vote path is verified in integration tests.) */
|
||||
ctrl_role_t role = am_i_primary_quorum(&ha, 1, NULL, 100ULL, 50ULL);
|
||||
ctrl_role_t role = am_i_primary_quorum(&ha, 1, NULL, 100ULL, 50ULL, "test-node");
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY, (int)role);
|
||||
TEST_ASSERT_GREATER_THAN_UINT64(initial_term,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue