feat(ha): add phase2_done, repl_ack_count, n_peers, client_ssl_ctx fields and CTRL_PRIMARY_COMMITTING enum

Task 2.1 of election-iomgr-refactor spec. Adds the new ha_state_t fields
required for the COMMITTING phase FSM, the CTRL_PRIMARY_COMMITTING role enum
value, and the committing_timeout_ms config field with its default constant.
ha_init() explicitly zero-initialises all new fields. Unit test verifies
zero-init and enum distinctness.

Requirements: 5.2, 8.1 (election-iomgr-refactor spec)
This commit is contained in:
Rongsong Shen 2026-05-30 07:40:38 +00:00
parent c3caab49a6
commit e42564b3bd
3 changed files with 79 additions and 14 deletions

View file

@ -94,6 +94,13 @@ int ha_init(ha_state_t *ha, const ha_config_t *cfg, int my_priority)
ha->standby_endpoints = NULL;
ha->primary_ctrl_fd = -1;
/* election-iomgr-refactor Task 2.1: explicit zero-init for documentation clarity
* (memset above already zeroes these, but being explicit is better documentation) */
ha->client_ssl_ctx = NULL;
ha->phase2_done = 0;
ha->n_peers = 0;
atomic_store_explicit(&ha->repl_ack_count, 0, memory_order_relaxed);
/* heartbeat_seen starts at 0: no heartbeat seen yet.
* ha_wait_for_heartbeat_or_timeout() calls sem_timedwait on this. */
sem_init(&ha->heartbeat_seen, 0, 0);

View file

@ -53,10 +53,11 @@ void ha_seed_jitter(uint64_t *seed, int my_priority);
/* ── Role enum ─────────────────────────────────────────────── */
typedef enum {
CTRL_PRIMARY = 0,
CTRL_STANDBY = 1,
CTRL_INIT = 2, /* Startup: listener open, awaiting heartbeat or election timeout */
CTRL_ELECTING = 3 /* Running quorum election via OP_requestVote */
CTRL_PRIMARY = 0,
CTRL_STANDBY = 1,
CTRL_INIT = 2, /* Startup: listener open, awaiting heartbeat or election timeout */
CTRL_ELECTING = 3, /* Running quorum election via OP_requestVote */
CTRL_PRIMARY_COMMITTING = 4 /* Primary waiting for quorum ACK on config commit */
} ctrl_role_t;
/* ── Election types ────────────────────────────────────────── */
@ -97,22 +98,24 @@ typedef struct {
/* ── HA configuration ──────────────────────────────────────── */
/* Default values used by cluster_init(). Tuned for a LAN with ≤5ms RTT. */
#define HA_DEFAULT_HEARTBEAT_INTERVAL_MS 3000
#define HA_DEFAULT_HEARTBEAT_TIMEOUT_MS 10000
#define HA_DEFAULT_ELECTION_TIMEOUT_MS 2000
#define HA_DEFAULT_PROBE_CONNECT_TIMEOUT_MS 500
#define HA_DEFAULT_MAX_PROMOTION_RETRIES 5
#define HA_DEFAULT_HEARTBEAT_INTERVAL_MS 3000
#define HA_DEFAULT_HEARTBEAT_TIMEOUT_MS 10000
#define HA_DEFAULT_ELECTION_TIMEOUT_MS 2000
#define HA_DEFAULT_PROBE_CONNECT_TIMEOUT_MS 500
#define HA_DEFAULT_MAX_PROMOTION_RETRIES 5
#define HA_DEFAULT_ELECTION_JITTER_MAX_MS 200
#define HA_DEFAULT_ELECTION_PRIORITY_STEP_MS 500
#define HA_DEFAULT_COMMITTING_TIMEOUT_MS 10000
typedef struct {
uint32_t heartbeat_interval_ms; /* Primary heartbeat send interval (min 2000 ms) */
uint32_t heartbeat_timeout_ms; /* Standby timeout before promoting (>= interval) */
uint32_t election_timeout_ms; /* Election probe shared deadline (min 500 ms) */
uint32_t probe_connect_timeout_ms; /* Per-probe TCP connect timeout */
uint32_t max_promotion_retries; /* Max consecutive failed promotion attempts */
uint32_t heartbeat_interval_ms; /* Primary heartbeat send interval (min 2000 ms) */
uint32_t heartbeat_timeout_ms; /* Standby timeout before promoting (>= interval) */
uint32_t election_timeout_ms; /* Election probe shared deadline (min 500 ms) */
uint32_t probe_connect_timeout_ms; /* Per-probe TCP connect timeout */
uint32_t max_promotion_retries; /* Max consecutive failed promotion attempts */
uint32_t election_jitter_max_ms; /* random jitter added to election timeout (default 200) */
uint32_t election_priority_step_ms; /* extra ms per priority level (default 500) */
uint32_t committing_timeout_ms; /* Quorum-ACK deadline for CTRL_PRIMARY_COMMITTING (default 10000) */
} ha_config_t;
typedef struct {
@ -157,6 +160,11 @@ typedef struct {
sem_t heartbeat_seen; /* Req 1: posted by heartbeat handler during CTRL_INIT */
uint64_t last_known_committed_config_tag; /* Req 8: from primary heartbeats */
_Atomic uint64_t committed_config_tag; /* Req 8.1: set on quorum ACK */
/* ── New fields added by election-iomgr-refactor spec (Task 2.1) ── */
void *client_ssl_ctx; /* SSL_CTX* (TLS_client_method); created in myapp_run */
int phase2_done; /* 1 after cluster_init_phase2 runs */
_Atomic int repl_ack_count; /* count of standbys that ACKed in COMMITTING state */
int n_peers; /* total candidate count (excluding self) */
} ha_state_t;
/* ── Public API ────────────────────────────────────────────── */

View file

@ -321,6 +321,54 @@ void test_am_i_primary_p1_deadline_expires(void)
printf("test_am_i_primary_p1_deadline_expires: PASS\n");
}
/* ── Task 2.1: ha_state_t new fields zero-initialised by ha_init ──────── */
/*
* Verify that ha_init zeroes the new fields added for election-iomgr-refactor:
* phase2_done, repl_ack_count, n_peers, client_ssl_ctx
* and that CTRL_PRIMARY_COMMITTING is a distinct enum value in ctrl_role_t.
* Also verifies ha_config_t.committing_timeout_ms default macro exists.
*
* Requirements: 5.2, 8.1 (election-iomgr-refactor spec)
*/
static void test_ha_init_zeroes_new_fields(void)
{
ha_state_t ha;
ha_config_t cfg = {
.heartbeat_interval_ms = 3000,
.heartbeat_timeout_ms = 10000,
.election_timeout_ms = 2000,
.probe_connect_timeout_ms = 500,
.max_promotion_retries = 5,
.committing_timeout_ms = HA_DEFAULT_COMMITTING_TIMEOUT_MS,
};
TEST_ASSERT_EQUAL_INT(0, ha_init(&ha, &cfg, 0));
/* phase2_done must be explicitly zero after ha_init */
TEST_ASSERT_EQUAL_INT(0, ha.phase2_done);
/* repl_ack_count is _Atomic int; must be zero after ha_init */
TEST_ASSERT_EQUAL_INT(0, atomic_load_explicit(&ha.repl_ack_count, memory_order_relaxed));
/* n_peers must be zero after ha_init */
TEST_ASSERT_EQUAL_INT(0, ha.n_peers);
/* client_ssl_ctx must be NULL after ha_init */
TEST_ASSERT_NULL(ha.client_ssl_ctx);
/* committing_timeout_ms stored in config */
TEST_ASSERT_EQUAL_UINT32(HA_DEFAULT_COMMITTING_TIMEOUT_MS, ha.config.committing_timeout_ms);
/* CTRL_PRIMARY_COMMITTING must be a distinct enum value */
TEST_ASSERT_NOT_EQUAL((int)CTRL_PRIMARY_COMMITTING, (int)CTRL_PRIMARY);
TEST_ASSERT_NOT_EQUAL((int)CTRL_PRIMARY_COMMITTING, (int)CTRL_STANDBY);
TEST_ASSERT_NOT_EQUAL((int)CTRL_PRIMARY_COMMITTING, (int)CTRL_INIT);
TEST_ASSERT_NOT_EQUAL((int)CTRL_PRIMARY_COMMITTING, (int)CTRL_ELECTING);
ha_fini(&ha);
printf("test_ha_init_zeroes_new_fields: PASS\n");
}
void test_ha_role(void)
{
RUN_TEST(test_ha_init_default_role_is_standby);
@ -335,6 +383,8 @@ void test_ha_role(void)
RUN_TEST(test_promote_increments_term);
RUN_TEST(test_demote_sets_standby_role);
RUN_TEST(test_sigusr1_sets_failover_requested);
/* Task 2.1: election-iomgr-refactor new fields */
RUN_TEST(test_ha_init_zeroes_new_fields);
}
/* ── P1-6 suite: am_i_primary() election ─────────────────────────────── */