feat(ha-repl-hardening): wire config push from decision loop (task 3.3)

ha_dispatch_repl_action_for_standby() calls push_config_to_standbys()
when PUSH_CONFIG decision. Hook seam for unit tests. Wired into
repl_ack_decode_and_dispatch after ha_process_standby_state.
oldest_wal_lsn=0 placeholder documented for task 3.4.
This commit is contained in:
Rongsong Shen 2026-06-04 17:23:46 +00:00
parent a475ef621f
commit d1c7872d73
4 changed files with 258 additions and 1 deletions

View file

@ -377,6 +377,109 @@ static void test_process_standby_state_multiple_peers(void)
printf("test_process_standby_state_multiple_peers: PASS\n");
}
/* ── Task 3.3: ha_dispatch_repl_action_for_standby PUSH_CONFIG wiring ──────
*
* ha_dispatch_repl_action_for_standby reads the standby's stored
* (standby_reported_tag, standby_reported_lsn), calls ha_evaluate_repl_action,
* and if the result is HA_REPL_PUSH_CONFIG calls push_config_to_standbys (in
* production) or ha_repl_push_config_for_standby_hook (in XCE_TESTING).
*
* Requirements: 1.4 (ha-repl-hardening), design §Unified Heartbeat-Driven Protocol
*/
#ifdef XCE_TESTING
extern int (*ha_repl_push_config_for_standby_hook)(void *c, int peer_idx);
#endif
/* Call-counting state for the hook */
static int g_push_config_for_standby_count = 0;
static int g_push_config_for_standby_idx = -1;
static int stub_repl_push_config_for_standby(void *c, int peer_idx)
{
(void)c;
g_push_config_for_standby_count++;
g_push_config_for_standby_idx = peer_idx;
return 0;
}
/*
* Test: when standby_reported_tag < committed_tag, ha_dispatch_repl_action_for_standby
* must call push_config_to_standbys (via hook in test mode).
*
* Steps:
* 1. Set ha->standby_reported_tag[0] = 3 (behind committed_tag=5)
* 2. Set ha->standby_reported_lsn[0] = 100 (at committed_lsn)
* 3. Call ha_dispatch_repl_action_for_standby(ha, NULL, 0, committed_tag=5, ...)
* 4. Assert hook called once with peer_idx=0
*
* Requirements: 1.4 (push_config when standby tag < committed_tag)
*/
static void test_dispatch_repl_action_push_config_when_tag_behind(void)
{
#ifdef XCE_TESTING
ha_repl_push_config_for_standby_hook = stub_repl_push_config_for_standby;
g_push_config_for_standby_count = 0;
g_push_config_for_standby_idx = -1;
ha_state_t *ha = build_ha_for_convergence_test();
/* standby[0] has stale config tag */
ha->standby_reported_tag[0] = 3;
ha->standby_reported_lsn[0] = 100;
int rc = ha_dispatch_repl_action_for_standby(
ha, /*c=*/NULL, /*peer_idx=*/0,
/*committed_tag=*/5, /*committed_lsn=*/100, /*oldest_wal_lsn=*/0);
(void)rc;
TEST_ASSERT_EQUAL_INT_MESSAGE(1, g_push_config_for_standby_count,
"PUSH_CONFIG: hook must fire once when standby_tag < committed_tag");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, g_push_config_for_standby_idx,
"PUSH_CONFIG: hook must receive correct peer_idx=0");
ha_fini(ha);
ha_repl_push_config_for_standby_hook = NULL;
printf("test_dispatch_repl_action_push_config_when_tag_behind: PASS\n");
#else
printf("test_dispatch_repl_action_push_config_when_tag_behind: SKIP (non-test build)\n");
#endif
}
/*
* Test: when standby is converged (tag == committed, lsn == committed),
* ha_dispatch_repl_action_for_standby must NOT call push_config hook.
*
* Requirements: decision tree — CONVERGED does not trigger push
*/
static void test_dispatch_repl_action_no_push_when_converged(void)
{
#ifdef XCE_TESTING
ha_repl_push_config_for_standby_hook = stub_repl_push_config_for_standby;
g_push_config_for_standby_count = 0;
g_push_config_for_standby_idx = -1;
ha_state_t *ha = build_ha_for_convergence_test();
/* standby[0] is fully converged */
ha->standby_reported_tag[0] = 5;
ha->standby_reported_lsn[0] = 100;
int rc = ha_dispatch_repl_action_for_standby(
ha, /*c=*/NULL, /*peer_idx=*/0,
/*committed_tag=*/5, /*committed_lsn=*/100, /*oldest_wal_lsn=*/0);
(void)rc;
TEST_ASSERT_EQUAL_INT_MESSAGE(0, g_push_config_for_standby_count,
"CONVERGED: hook must NOT fire when standby is already converged");
ha_fini(ha);
ha_repl_push_config_for_standby_hook = NULL;
printf("test_dispatch_repl_action_no_push_when_converged: PASS\n");
#else
printf("test_dispatch_repl_action_no_push_when_converged: SKIP (non-test build)\n");
#endif
}
/* ── Task 3.2: ha_evaluate_repl_action unified decision tree ─────────────── */
/*
@ -491,6 +594,9 @@ void test_ha_role(void)
RUN_TEST(test_evaluate_repl_action_converged);
RUN_TEST(test_evaluate_repl_action_send_snapshot);
RUN_TEST(test_evaluate_repl_action_stream_wal);
/* Task 3.3: ha_dispatch_repl_action_for_standby PUSH_CONFIG wiring */
RUN_TEST(test_dispatch_repl_action_push_config_when_tag_behind);
RUN_TEST(test_dispatch_repl_action_no_push_when_converged);
}
/* test_am_i_primary suite deleted — am_i_primary() removed (election-iomgr-refactor) */