feat(ha-config-bootstrap): complete quorum election with mutex safety (task 4.3)

- am_i_primary_quorum(): per-peer pthreads, connect_ssl_raw + OP_requestVote
- pthread_mutex_t lock on ha_elect_ctx_t guards votes_granted/n_replied/max_resp
- done_posted guard in ha_elect_evaluate_and_signal prevents double sem_post
- Replace am_i_primary() with am_i_primary_quorum() in myapp.c
- Delete ha_check_priority_stepdown and ha_check_heartbeat_nocluster
- INFO logs: election outcome (Req 10.1), FSM transitions (Req 10.5)
- 24 election tests + 24 startup tests, 0 failures
This commit is contained in:
Rongsong Shen 2026-05-28 08:28:35 +00:00
parent 0f88d5d32a
commit 0c9c720b03
4 changed files with 390 additions and 98 deletions

View file

@ -68,30 +68,54 @@ int myapp_run(myconf_t* myconf)
vtaskmgr_init(&(c.vtaskmgr), nvcpus);
}
/* Election: determine role before loading config.
* Capture result once — am_i_primary() probes peers and is expensive. */
int is_primary = am_i_primary(&c);
if (is_primary) {
/* Primary: load config from conf/current (FATAL if missing) */
rc = load_primary_config(&c);
if (rc != 0) {
cluster_fini(&c);
myconf_fini(myconf);
return rc;
/* Election: quorum-based role determination (Req 2.1, 2.9).
* Build the peer pointer array (excluding self) for am_i_primary_quorum().
* peers_cfg.candidates includes this node; n_peers = candidates_count.
* my_priority is the index of this node in the candidates list. */
{
const xce_cluster_config_t *pcfg = &myconf->peers_cfg;
int n_peers = (pcfg->candidates_count > 0) ? pcfg->candidates_count : 1;
/* Build array of pointers to other candidates (excluding self by index) */
xce_candidate_t *peer_ptrs[MAX_HA_CANDIDATES];
int n_other = 0;
for (int i = 0; i < pcfg->candidates_count; i++) {
if (i != c.ha.my_priority)
peer_ptrs[n_other++] = (xce_candidate_t *)&pcfg->candidates[i];
}
/* Record election outcome so cluster_init_phase2() sees CTRL_PRIMARY.
* ha_init() unconditionally defaults ha->role to CTRL_STANDBY. */
atomic_store_explicit(&c.ha.role, (int32_t)CTRL_PRIMARY,
memory_order_release);
} else {
/* Standby: fetch config from primary before Phase 2 */
rc = fetch_config_from_primary(&c);
if (rc != 0) {
cluster_fini(&c);
myconf_fini(myconf);
return rc;
uint64_t my_tag = myconf->config_version_tag;
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);
if (elected == CTRL_PRIMARY) {
/* Primary: load config from conf/current (FATAL if missing) */
rc = load_primary_config(&c);
if (rc != 0) {
cluster_fini(&c);
myconf_fini(myconf);
return rc;
}
/* am_i_primary_quorum logs FSM transition; set role explicitly */
atomic_store_explicit(&c.ha.role, (int32_t)CTRL_PRIMARY,
memory_order_release);
} else {
/* Standby: fetch config from primary before Phase 2 */
rc = fetch_config_from_primary(&c);
if (rc != 0) {
cluster_fini(&c);
myconf_fini(myconf);
return rc;
}
/* Role stays CTRL_STANDBY — already set by ha_init(). */
}
/* Role stays CTRL_STANDBY — already set by ha_init(). */
}
/* Req 4.4: warn if config file contains deprecated 'vcpus' key */