feat(ha-repl-hardening): add per-standby convergence tracking arrays (task 1.4)

Add standby_reported_tag/lsn/converged arrays to ha_state_t, sized
MAX_HA_CANDIDATES. Zero-init via existing memset in ha_init().
Unit test verifies zero-init and writability.
This commit is contained in:
Rongsong Shen 2026-06-04 14:14:23 +00:00
parent 0121bc0683
commit e3ca775f0d
4 changed files with 55 additions and 1 deletions

View file

@ -32,7 +32,7 @@
- _Requirements: 4.1, 4.2_
- _Boundary: Wire Protocol (controller.capnp)_
- [ ] 1.4 Add per-standby convergence tracking fields to `ha_state_t`
- [x] 1.4 Add per-standby convergence tracking fields to `ha_state_t`
- Add `standby_reported_tag[MAX_HA_CANDIDATES]`, `standby_reported_lsn[MAX_HA_CANDIDATES]`, `standby_converged[MAX_HA_CANDIDATES]` arrays
- Zero-initialize in `ha_init()`; these fields are read by the convergence evaluator in task 3.1
- Unit test: verify fields are zero after `ha_init()` and writable from test context

View file

@ -689,6 +689,11 @@ int ha_init(ha_state_t *ha, const ha_config_t *cfg, int my_priority)
* Written by ha_on_heartbeat_received (task 2.1); read by liveness_poll_fn. */
ha->last_activity = 0;
/* ha-repl-hardening Task 1.4: per-standby convergence tracking arrays.
* memset(ha, 0, sizeof(*ha)) above already zeroes these arrays; the explicit
* note documents the invariant: on entry to CTRL_PRIMARY_COMMITTING every
* standby starts as not-converged (tag=0, lsn=0, converged=false). */
/* 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

@ -177,6 +177,10 @@ typedef struct {
void *committing_timeout_ctx; /* committing_timeout_ctx_t* for cancel on step-down */
/* ── New fields added by ha-heartbeat-liveness spec (Task 1.1) ── */
time_t last_activity; /* CLOCK_MONOTONIC seconds of last primary message; 0 = none yet */
/* ── New fields added by ha-repl-hardening spec (Task 1.4) ──────── */
uint64_t standby_reported_tag[MAX_HA_CANDIDATES]; /* Last config_tag reported by each standby */
uint64_t standby_reported_lsn[MAX_HA_CANDIDATES]; /* Last applied_lsn reported by each standby */
bool standby_converged[MAX_HA_CANDIDATES]; /* Whether standby[i] has converged with primary */
} ha_state_t;
/* ── Public API ────────────────────────────────────────────── */

View file

@ -194,6 +194,49 @@ static void test_ha_init_zeroes_new_fields(void)
printf("test_ha_init_zeroes_new_fields: PASS\n");
}
/*
* Task 1.4: per-standby convergence tracking fields zero-initialised by ha_init.
*
* Verifies:
* - standby_reported_tag[i] == 0 for all i after ha_init()
* - standby_reported_lsn[i] == 0 for all i after ha_init()
* - standby_converged[i] == false for all i after ha_init()
* - fields are writable from test context
*
* Requirements: 2.1 (ha-repl-hardening spec)
*/
static void test_ha_init_zeroes_standby_convergence_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));
/* All per-standby tracking arrays must be zero/false after ha_init */
for (int i = 0; i < MAX_HA_CANDIDATES; i++) {
TEST_ASSERT_EQUAL_UINT64(0, ha.standby_reported_tag[i]);
TEST_ASSERT_EQUAL_UINT64(0, ha.standby_reported_lsn[i]);
TEST_ASSERT_FALSE(ha.standby_converged[i]);
}
/* Fields must be writable */
ha.standby_reported_tag[0] = 42ULL;
ha.standby_reported_lsn[0] = 100ULL;
ha.standby_converged[0] = true;
TEST_ASSERT_EQUAL_UINT64(42ULL, ha.standby_reported_tag[0]);
TEST_ASSERT_EQUAL_UINT64(100ULL, ha.standby_reported_lsn[0]);
TEST_ASSERT_TRUE(ha.standby_converged[0]);
ha_fini(&ha);
printf("test_ha_init_zeroes_standby_convergence_fields: PASS\n");
}
void test_ha_role(void)
{
RUN_TEST(test_ha_init_default_role_is_standby);
@ -207,6 +250,8 @@ void test_ha_role(void)
/* Watchdog / SIGUSR1 tests deleted */
/* Task 2.1: election-iomgr-refactor new fields */
RUN_TEST(test_ha_init_zeroes_new_fields);
/* Task 1.4: ha-repl-hardening per-standby convergence fields */
RUN_TEST(test_ha_init_zeroes_standby_convergence_fields);
}
/* test_am_i_primary suite deleted — am_i_primary() removed (election-iomgr-refactor) */