fix(election-iomgr-refactor): Raft election, config decode, and RDHUP chain
- ha.c: self-vote in ha_start_election_async (Raft invariant) - ha.c: heartbeat_timeout_ms for hb_timeout_thread (not election_timeout_ms) - ha.c: clear voted_for before RDHUP jitter (stale vote from dead primary) - ha.c: same-term voted_for check in RDHUP jitter - config_repl.c/h: use xce_decode for upgradeConfig (capnp extraction before capn_free) - cluster.c: fix upgradeConfig handler direction (standbys apply, primaries skip) - cluster.c: remove raw_pkt_copy hack; use req directly - join.c: push config to newly-joined standbys - typemap.h/request.h/request.c: raw_sha256/raw_bytes fields for decoded upgradeConfig - test_ha_failover.c: update heartbeat timeout test to use g_fast_cfg
This commit is contained in:
parent
1ded1022fb
commit
dbb750ebd3
8 changed files with 198 additions and 102 deletions
|
|
@ -1808,6 +1808,16 @@ void ha_start_election_async(ha_state_t *ha, cluster_t *c,
|
|||
atomic_fetch_add_explicit(&ha->term, 1, memory_order_acq_rel);
|
||||
uint64_t cur_term = atomic_load_explicit(&ha->term, memory_order_acquire);
|
||||
|
||||
/* Record self-vote before sending RequestVote RPCs. This prevents granting
|
||||
* a vote to a concurrent candidate in the same term (split-brain guard). */
|
||||
if (my_name) {
|
||||
pthread_mutex_lock(&ha->lock);
|
||||
ha->voted_for_term = cur_term;
|
||||
strncpy(ha->voted_for_name, my_name, sizeof(ha->voted_for_name) - 1);
|
||||
ha->voted_for_name[sizeof(ha->voted_for_name) - 1] = '\0';
|
||||
pthread_mutex_unlock(&ha->lock);
|
||||
}
|
||||
|
||||
/* Transition to CTRL_ELECTING */
|
||||
atomic_store_explicit(&ha->role, (int32_t)CTRL_ELECTING, memory_order_release);
|
||||
INFO("HA FSM: → CTRL_ELECTING (async, term=%" PRIu64 ", n_peers=%d)",
|
||||
|
|
@ -2217,29 +2227,37 @@ void ha_on_primary_ctrl_fd_rdhup(ha_state_t *ha, cluster_t *c)
|
|||
if (committed > 0)
|
||||
persist_truncate_wal_above(&c->persist, committed);
|
||||
|
||||
/* Clear voted_for so the jitter check below reflects NEW votes cast during
|
||||
* the RDHUP election, not the stale startup voted_for (different term). */
|
||||
/* Clear voted_for: the old primary is dead; any vote from a previous term
|
||||
* is stale and must not block the new election. */
|
||||
pthread_mutex_lock(&ha->lock);
|
||||
ha->voted_for_term = 0;
|
||||
memset(ha->voted_for_name, 0, sizeof(ha->voted_for_name));
|
||||
pthread_mutex_unlock(&ha->lock);
|
||||
|
||||
/* Priority-based jitter before RDHUP election to avoid split-brain:
|
||||
* lower-priority nodes (higher priority index) wait longer, giving the
|
||||
* highest-priority node time to win before others start their elections. */
|
||||
/* Priority-based jitter before RDHUP election.
|
||||
* The highest-priority (index 0) node starts immediately; lower-priority
|
||||
* nodes sleep for (priority * election_timeout_ms). During the sleep they
|
||||
* may receive a RequestVote from the priority-0 node — if so, they grant
|
||||
* it (setting voted_for) and then skip their own election below. */
|
||||
if (ha->my_priority > 0) {
|
||||
uint32_t step_ms = ha->config.election_priority_step_ms
|
||||
? ha->config.election_priority_step_ms
|
||||
: HA_DEFAULT_ELECTION_PRIORITY_STEP_MS;
|
||||
uint32_t step_ms = ha->config.election_timeout_ms
|
||||
? ha->config.election_timeout_ms
|
||||
: HA_DEFAULT_ELECTION_TIMEOUT_MS;
|
||||
long delay_ns = (long)ha->my_priority * (long)step_ms * 1000000L;
|
||||
struct timespec jitter = {
|
||||
.tv_sec = delay_ns / 1000000000L,
|
||||
.tv_nsec = delay_ns % 1000000000L,
|
||||
};
|
||||
nanosleep(&jitter, NULL);
|
||||
/* If already voted during jitter (from the winner's vote request), skip */
|
||||
/* Skip our election if we already voted for ANOTHER candidate in the
|
||||
* current term during the jitter — meaning the priority-0 node sent
|
||||
* us a RequestVote, we granted it, and should not compete. */
|
||||
pthread_mutex_lock(&ha->lock);
|
||||
int already_voted = (ha->voted_for_name[0] != '\0');
|
||||
uint64_t cur_term = atomic_load_explicit(&ha->term, memory_order_acquire);
|
||||
const char *myname_ptr = c && c->myconf ? c->myconf->name : "";
|
||||
int already_voted = (ha->voted_for_name[0] != '\0' &&
|
||||
ha->voted_for_term == cur_term &&
|
||||
strcmp(ha->voted_for_name, myname_ptr) != 0);
|
||||
pthread_mutex_unlock(&ha->lock);
|
||||
if (already_voted) {
|
||||
INFO("HA: RDHUP — already voted during jitter, skipping own election");
|
||||
|
|
@ -2300,7 +2318,9 @@ static void *hb_timeout_thread(void *arg)
|
|||
ha_state_t *ha = t->ha;
|
||||
|
||||
while (!atomic_load(&t->disarmed)) {
|
||||
/* Compute deadline: election_timeout_ms + rand_jitter */
|
||||
/* Compute deadline: heartbeat_timeout_ms + rand_jitter.
|
||||
* Must use heartbeat_timeout_ms (not election_timeout_ms) so standbys
|
||||
* don't falsely trigger failover while the primary is in COMMITTING. */
|
||||
uint32_t jitter_ms = 0;
|
||||
if (ha->config.election_jitter_max_ms > 0) {
|
||||
struct timespec now;
|
||||
|
|
@ -2308,7 +2328,7 @@ static void *hb_timeout_thread(void *arg)
|
|||
unsigned int seed = (unsigned int)now.tv_nsec;
|
||||
jitter_ms = (uint32_t)(rand_r(&seed) % ha->config.election_jitter_max_ms);
|
||||
}
|
||||
uint32_t total_ms = ha->config.election_timeout_ms + jitter_ms;
|
||||
uint32_t total_ms = ha->config.heartbeat_timeout_ms + jitter_ms;
|
||||
|
||||
struct timespec deadline;
|
||||
clock_gettime(CLOCK_REALTIME, &deadline);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue