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)
|
if (ha->jitter_seed == 0)
|
||||||
ha_seed_jitter(&ha->jitter_seed, ha->my_priority);
|
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;
|
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) {
|
if (ha->config.election_jitter_max_ms > 0) {
|
||||||
to_ms += (long)(xorshift64_next(&ha->jitter_seed)
|
to_ms += (long)(xorshift64_next(&ha->jitter_seed)
|
||||||
% ((uint64_t)ha->config.election_jitter_max_ms + 1));
|
% ((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_tag; /* config_tag sent in the request */
|
||||||
uint64_t my_lsn; /* WAL LSN sent in the request */
|
uint64_t my_lsn; /* WAL LSN sent in the request */
|
||||||
int timeout_ms; /* Connect + I/O timeout */
|
int timeout_ms; /* Connect + I/O timeout */
|
||||||
|
const char *my_name; /* Candidate node name for voted_for deduplication */
|
||||||
} vote_thread_ctx_t;
|
} vote_thread_ctx_t;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -1097,7 +1101,7 @@ static void *vote_request_thread(void *arg)
|
||||||
request_vote_req_t vote_req;
|
request_vote_req_t vote_req;
|
||||||
memset(&vote_req, 0, sizeof(vote_req));
|
memset(&vote_req, 0, sizeof(vote_req));
|
||||||
vote_req.term = vctx->my_term;
|
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.config_tag = vctx->my_tag;
|
||||||
vote_req.last_log_lsn = vctx->my_lsn;
|
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,
|
int n_peers,
|
||||||
xce_candidate_t **peers,
|
xce_candidate_t **peers,
|
||||||
uint64_t my_config_tag,
|
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 */
|
/* Req 2.9: single-node self-elects immediately */
|
||||||
if (n_peers == 1) {
|
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].my_lsn = my_lsn;
|
||||||
vctxs[i].timeout_ms =
|
vctxs[i].timeout_ms =
|
||||||
(int)ha->config.probe_connect_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]);
|
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_tag = c->myconf->config_version_tag;
|
||||||
uint64_t my_lsn = atomic_load_explicit(&c->persist.committed_lsn,
|
uint64_t my_lsn = atomic_load_explicit(&c->persist.committed_lsn,
|
||||||
memory_order_acquire);
|
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)
|
if (new_role == CTRL_PRIMARY)
|
||||||
INFO("HA: RDHUP recovery → CTRL_PRIMARY (Req 6.8)");
|
INFO("HA: RDHUP recovery → CTRL_PRIMARY (Req 6.8)");
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -347,7 +347,8 @@ ctrl_role_t am_i_primary_quorum(ha_state_t *ha,
|
||||||
int n_peers,
|
int n_peers,
|
||||||
xce_candidate_t **peers,
|
xce_candidate_t **peers,
|
||||||
uint64_t my_config_tag,
|
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.
|
* 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,
|
uint64_t my_lsn = (uint64_t)atomic_load_explicit(&c.ha.applied_lsn,
|
||||||
memory_order_acquire);
|
memory_order_acquire);
|
||||||
ctrl_role_t elected = am_i_primary_quorum(
|
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) {
|
if (elected == CTRL_PRIMARY) {
|
||||||
final_role = 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,
|
ctrl_role_t am_i_primary_quorum(ha_state_t *ha, int n_peers,
|
||||||
xce_candidate_t **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,
|
void handle_request_vote_reply(ha_state_t *ha, ha_elect_ctx_t *ctx,
|
||||||
int peer_idx, uint64_t term,
|
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) */
|
1, /* n_peers = 1 (single node) */
|
||||||
NULL, /* no other peers */
|
NULL, /* no other peers */
|
||||||
500ULL, /* my_config_tag */
|
500ULL, /* my_config_tag */
|
||||||
100ULL /* my_lsn */);
|
100ULL, /* my_lsn */
|
||||||
|
"test-node");
|
||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY, (int)role);
|
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.
|
/* Single-node: am_i_primary_quorum must self-elect and bump the term.
|
||||||
* (Multi-node split-vote path is verified in integration tests.) */
|
* (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_EQUAL_INT(CTRL_PRIMARY, (int)role);
|
||||||
TEST_ASSERT_GREATER_THAN_UINT64(initial_term,
|
TEST_ASSERT_GREATER_THAN_UINT64(initial_term,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue