From e42564b3bdc7f995b7599da6275fce9718fb5c7a Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Sat, 30 May 2026 07:40:38 +0000 Subject: [PATCH] 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) --- src/controller/ha.c | 7 ++++ src/controller/ha.h | 36 +++++++++++------- src/controller/testsuite/test_ha_role.c | 50 +++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/src/controller/ha.c b/src/controller/ha.c index de99c2c4..5c182506 100644 --- a/src/controller/ha.c +++ b/src/controller/ha.c @@ -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); diff --git a/src/controller/ha.h b/src/controller/ha.h index a3ccd645..c996a1de 100644 --- a/src/controller/ha.h +++ b/src/controller/ha.h @@ -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 ────────────────────────────────────────────── */ diff --git a/src/controller/testsuite/test_ha_role.c b/src/controller/testsuite/test_ha_role.c index 184870cd..5b6642b8 100644 --- a/src/controller/testsuite/test_ha_role.c +++ b/src/controller/testsuite/test_ha_role.c @@ -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 ─────────────────────────────── */