feat(ha-convergence-recovery): self-restart primitive + demotion convergence (task 2.2)

This commit is contained in:
Rongsong Shen 2026-06-14 03:47:12 +00:00
parent c8e55947c2
commit 09103a3141
7 changed files with 337 additions and 43 deletions

View file

@ -369,7 +369,7 @@ static void test_demotion_from_standby_no_truncation_no_restart(void)
printf("test_demotion_from_standby_no_truncation_no_restart: checked\n");
}
/* Regression: demotion from ELECTING/INIT must NOT truncate or restart. */
/* Regression: demotion from ELECTING must NOT truncate or restart. */
static void test_demotion_from_electing_no_truncation_no_restart(void)
{
dc_setup("electing", /*n_peers=*/3, /*n_standbys=*/2,
@ -403,6 +403,42 @@ static void test_demotion_from_electing_no_truncation_no_restart(void)
printf("test_demotion_from_electing_no_truncation_no_restart: checked\n");
}
/* Regression: demotion from INIT must NOT truncate or restart (mirror of the
* ELECTING case — a CTRL_INIT node has no registered persistence hooks and no
* provisional suffix, so the was-PRIMARY convergence branch must not fire). */
static void test_demotion_from_init_no_truncation_no_restart(void)
{
dc_setup("init", /*n_peers=*/3, /*n_standbys=*/2,
/*appended_lsn=*/9, /*n_vcpus=*/8);
atomic_store(&g_dc.persist.committed_lsn, 4);
/* No registered hooks on a starting node; force role to INIT. */
TEST_ASSERT_EQUAL_INT(0, ha_demote_to_standby(&g_dc)); /* clears hooks */
atomic_store_explicit(&g_dc.ha.role, (int32_t)CTRL_INIT,
memory_order_release);
ha_restart_hook = dc_restart_hook;
ha_fatal_hook = dc_fatal_hook;
uint64_t tip_before = g_dc.persist.current_lsn;
int tasks_before = dc_task_count(&g_dc.delaymgr);
/* Act. */
raft_cb_become_follower_with_cluster_for_test(&g_dc, /*term=*/8);
/* Assert. */
TEST_ASSERT_EQUAL_INT_MESSAGE(tasks_before,
dc_task_count(&g_dc.delaymgr),
"demotion from INIT must schedule NO convergence task (regression)");
TEST_ASSERT_EQUAL_UINT64_MESSAGE(tip_before, g_dc.persist.current_lsn,
"demotion from INIT must NOT truncate the WAL (regression)");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, atomic_load(&g_restart_calls),
"demotion from INIT must NOT self-restart (regression)");
dc_teardown();
printf("test_demotion_from_init_no_truncation_no_restart: checked\n");
}
/* 1.7/2.4 (integration, in-process half): append a committed prefix + an
* uncommitted suffix, run the demotion seam, then replay the SURVIVING WAL into
* a FRESH persist context. The replayed state must contain the committed prefix
@ -492,6 +528,7 @@ void test_demotion_convergence(void)
RUN_TEST(test_self_restart_failure_invokes_fatal_hook);
RUN_TEST(test_demotion_from_standby_no_truncation_no_restart);
RUN_TEST(test_demotion_from_electing_no_truncation_no_restart);
RUN_TEST(test_demotion_from_init_no_truncation_no_restart);
RUN_TEST(test_demotion_replay_yields_committed_only);
#else
printf("test_demotion_convergence: SKIP (non-test build)\n");