chore: checkpoint all prior work for clean state

Captures all uncommitted work from ha-config-bootstrap,
ha-heartbeat-liveness, and associated spec/tooling updates:

- src: ha.c, ha.h, cluster.c, config_repl.c, myapp.c, raft.c —
  heartbeat liveness, RDHUP integration, committing phase fixes
- tests: test_ha_role.c, test_config_repl.c, test_ha_rdhup_integration.c,
  test_p1_integration.c, test_p3_hardening.c, scheduler test
- specs: election-iomgr-refactor, ha-heartbeat-liveness,
  ha-peer-connection-reuse, raft-library (new); ha-config-bootstrap
  and election-iomgr-refactor task/research updates
- tooling: .wolf/ state, ha/data cluster fixtures, raft/raft.tla,
  src/build symlink, xce-build skill update
This commit is contained in:
Rongsong Shen 2026-06-04 13:04:16 +00:00
parent 15bd291bdd
commit e31673dd88
66 changed files with 122422 additions and 860 deletions

View file

@ -88,72 +88,8 @@ static void test_ha_promote_sets_primary_role(void)
printf("test_ha_promote_sets_primary_role: PASS\n");
}
/* ── P1-3 watchdog tests ────────────────────────────────────── */
/* ha_start_watchdog() must be callable after ha_init() */
static void test_watchdog_starts_and_stops(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,
};
TEST_ASSERT_EQUAL_INT(0, ha_init(&ha, &cfg, 0));
TEST_ASSERT_EQUAL_INT(0, ha_start_watchdog(&ha));
/* brief sleep to let the thread wake at least once */
struct timespec ts = { 0, 50 * 1000000L }; /* 50ms */
nanosleep(&ts, NULL);
ha_stop_watchdog(&ha); /* sets stop flag + pthread_join */
ha_fini(&ha);
printf("test_watchdog_starts_and_stops: PASS\n");
}
/* ha_check_heartbeat_nocluster(): when in CTRL_STANDBY with no primary_ep,
* must not promote */
static void test_heartbeat_no_action_when_no_primary(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,
};
ha_init(&ha, &cfg, 1 /* not highest priority */);
/* No primary_host set — watchdog must not promote */
ha_check_heartbeat_nocluster(&ha); /* test-only accessor */
TEST_ASSERT_EQUAL_INT(CTRL_STANDBY,
(int)atomic_load_explicit(&ha.role, memory_order_acquire));
ha_fini(&ha);
printf("test_heartbeat_no_action_when_no_primary: PASS\n");
}
/* ha_check_heartbeat_nocluster(): when last_heartbeat is stale (timeout exceeded)
* and primary_host is empty (no known primary), must not crash or change role */
static void test_heartbeat_stale_no_crash_without_primary(void)
{
ha_state_t ha;
ha_config_t cfg = {
.heartbeat_interval_ms = 2000, /* minimum valid */
.heartbeat_timeout_ms = 3000, /* >= interval */
.election_timeout_ms = 2000,
.probe_connect_timeout_ms = 500,
.max_promotion_retries = 5,
};
/* Staleness is injected manually below; timeout value only needs to be valid */
TEST_ASSERT_EQUAL_INT(0, ha_init(&ha, &cfg, 0));
/* Set last_heartbeat to a time far in the past to simulate timeout */
clock_gettime(CLOCK_MONOTONIC, &ha.last_heartbeat);
ha.last_heartbeat.tv_sec -= 10; /* 10 seconds ago */
ha_check_heartbeat_nocluster(&ha);
/* Should not crash. Role may or may not change — but no segfault. */
ha_fini(&ha);
printf("test_heartbeat_stale_no_crash_without_primary: PASS\n");
}
/* Watchdog / SIGUSR1 tests deleted — watchdog pthread replaced by
* ha_arm_heartbeat_timeout_vtask (delaymgr); failover_requested removed */
/* ── P1-3a tests ─────────────────────────────────────────────── */
@ -206,120 +142,9 @@ static void test_demote_sets_standby_role(void)
printf("test_demote_sets_standby_role: PASS\n");
}
/* SIGUSR1 must set failover_requested via async-signal-safe atomic store */
static void test_sigusr1_sets_failover_requested(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,
};
ha_init(&ha, &cfg, 0);
/* test_sigusr1_sets_failover_requested deleted — SIGUSR1 / failover_requested removed */
/* Install real SIGUSR1 handler that uses only the atomic flag */
ha_install_sigusr1(&ha);
/* Send SIGUSR1 to self */
raise(SIGUSR1);
/* Handler must have set failover_requested */
TEST_ASSERT_TRUE(
atomic_load_explicit(&ha.failover_requested, memory_order_acquire) != 0);
/* Restore default SIGUSR1 handler */
signal(SIGUSR1, SIG_DFL);
ha_fini(&ha);
printf("test_sigusr1_sets_failover_requested: PASS\n");
}
/* ── P1-6: am_i_primary() election tests ────────────────────────────── */
/* test: priority-0 with no peers → immediately primary (no probing needed) */
void test_am_i_primary_p0_no_peers(void)
{
cluster_t c;
memset(&c, 0, sizeof(c));
myconf_t myconf;
memset(&myconf, 0, sizeof(myconf));
c.myconf = &myconf;
ha_config_t cfg = {
.heartbeat_interval_ms = HA_DEFAULT_HEARTBEAT_INTERVAL_MS,
.heartbeat_timeout_ms = HA_DEFAULT_HEARTBEAT_TIMEOUT_MS,
.election_timeout_ms = 500, /* minimum valid value */
.probe_connect_timeout_ms = 100,
.max_promotion_retries = 1,
.election_jitter_max_ms = 0,
.election_priority_step_ms = 0,
};
TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &cfg, 0));
TEST_ASSERT_EQUAL_INT(1, am_i_primary(&c));
ha_fini(&c.ha);
printf("test_am_i_primary_p0_no_peers: PASS\n");
}
/* test: priority-0 with peers → still primary (never probes higher-priority nodes) */
void test_am_i_primary_p0_with_peers(void)
{
cluster_t c;
memset(&c, 0, sizeof(c));
myconf_t myconf;
memset(&myconf, 0, sizeof(myconf));
myconf.peers_cfg.candidates_count = 2;
strncpy(myconf.peers_cfg.candidates[0].hostname, "127.0.0.1", XCE_MAXNAME_LEN - 1);
myconf.peers_cfg.candidates[0].port = 19900;
strncpy(myconf.peers_cfg.candidates[1].hostname, "127.0.0.1", XCE_MAXNAME_LEN - 1);
myconf.peers_cfg.candidates[1].port = 19901;
c.myconf = &myconf;
ha_config_t cfg = {
.heartbeat_interval_ms = HA_DEFAULT_HEARTBEAT_INTERVAL_MS,
.heartbeat_timeout_ms = HA_DEFAULT_HEARTBEAT_TIMEOUT_MS,
.election_timeout_ms = 500, /* minimum valid value */
.probe_connect_timeout_ms = 100,
.max_promotion_retries = 1,
.election_jitter_max_ms = 0,
.election_priority_step_ms = 0,
};
TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &cfg, 0));
TEST_ASSERT_EQUAL_INT(1, am_i_primary(&c));
ha_fini(&c.ha);
printf("test_am_i_primary_p0_with_peers: PASS\n");
}
/* test: priority-1 with no higher-priority peer alive → deadline expires → primary */
void test_am_i_primary_p1_deadline_expires(void)
{
cluster_t c;
memset(&c, 0, sizeof(c));
myconf_t myconf;
memset(&myconf, 0, sizeof(myconf));
/* Point to ports where nothing is listening */
myconf.peers_cfg.candidates_count = 2;
strncpy(myconf.peers_cfg.candidates[0].hostname, "127.0.0.1", XCE_MAXNAME_LEN - 1);
myconf.peers_cfg.candidates[0].port = 19910; /* nothing listening */
strncpy(myconf.peers_cfg.candidates[1].hostname, "127.0.0.1", XCE_MAXNAME_LEN - 1);
myconf.peers_cfg.candidates[1].port = 19911;
c.myconf = &myconf;
ha_config_t cfg = {
.heartbeat_interval_ms = HA_DEFAULT_HEARTBEAT_INTERVAL_MS,
.heartbeat_timeout_ms = HA_DEFAULT_HEARTBEAT_TIMEOUT_MS,
.election_timeout_ms = 600, /* expires quickly; > 500ms floor */
.probe_connect_timeout_ms = 200,
.max_promotion_retries = 1,
.election_jitter_max_ms = 0,
.election_priority_step_ms = 0,
};
TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &cfg, 1)); /* priority 1 — must probe priority 0 */
int result = am_i_primary(&c);
TEST_ASSERT_EQUAL_INT(1, result); /* no peer responded → self-declare primary */
ha_fini(&c.ha);
printf("test_am_i_primary_p1_deadline_expires: PASS\n");
}
/* am_i_primary() tests deleted — function removed (election-iomgr-refactor) */
/* ── Task 2.1: ha_state_t new fields zero-initialised by ha_init ──────── */
@ -376,22 +201,12 @@ void test_ha_role(void)
RUN_TEST(test_ha_init_rejects_short_election_timeout);
RUN_TEST(test_ha_init_rejects_timeout_less_than_interval);
RUN_TEST(test_ha_promote_sets_primary_role);
RUN_TEST(test_watchdog_starts_and_stops);
RUN_TEST(test_heartbeat_no_action_when_no_primary);
RUN_TEST(test_heartbeat_stale_no_crash_without_primary);
/* P1-3a */
RUN_TEST(test_promote_increments_term);
RUN_TEST(test_demote_sets_standby_role);
RUN_TEST(test_sigusr1_sets_failover_requested);
/* Watchdog / SIGUSR1 tests deleted */
/* Task 2.1: election-iomgr-refactor new fields */
RUN_TEST(test_ha_init_zeroes_new_fields);
}
/* ── P1-6 suite: am_i_primary() election ─────────────────────────────── */
void test_am_i_primary(void)
{
RUN_TEST(test_am_i_primary_p0_no_peers);
RUN_TEST(test_am_i_primary_p0_with_peers);
RUN_TEST(test_am_i_primary_p1_deadline_expires);
}
/* test_am_i_primary suite deleted — am_i_primary() removed (election-iomgr-refactor) */