feat(ha-commit-engine): engine instantiation + sync-append path switch (task 5.3)

cluster_t embeds commit_engine_t; init in cluster_init_phase2 after the
vtaskmgr guard (idempotence-guarded — phase2 can run twice), wiring
repl_set_commit_engine + ha.repl back-pointer before any standby can
register; fini after iomgr/vtask shutdown. persist_wal_sync_append
passes written_lsn; engine-wired path drops -ENODEV and the
committed_lsn store (engine owns advancement); LSN-0 first-entry
routes to legacy per the accepted interim. p2 -ENODEV pin updated to
the Req 6.3 contract. 34/34 + 6/6 green through honest delegation;
census exactly at the 3 validated baselines; production binary links.
This commit is contained in:
Rongsong Shen 2026-06-12 00:16:22 +00:00
parent 6600fd5d1f
commit f0d6074d4e
6 changed files with 142 additions and 50 deletions

View file

@ -18,6 +18,7 @@
#include <unity/unity.h>
#include "commit.h"
#include "config.h"
#include "persist.h"
#include "repl.h"
@ -219,44 +220,50 @@ static void test_repl_api_completeness(void)
}
/* ------------------------------------------------------------------ */
/* Test 4b: Req 4.4 / single-node path — committed_lsn NOT advanced by
* persist_wal_sync_append when no standbys are registered.
/* Test 4b: single-node path — production contract (ha-commit-engine Req 6.3).
*
* persist_wal_sync_append uses has_standbys flag. With no standbys it skips
* repl_wait_majority_ack entirely and sets sync_err = -ENODEV (degraded mode).
* The committed_lsn therefore remains unchanged. This is intentional: a
* single-node controller operates in degraded mode — WAL writes are locally
* durable but not majority-committed. Req 4.4 convergence only applies once
* standbys are registered and ACK.
* UPDATED by ha-commit-engine task 5.3: this test previously pinned the
* `has_standbys → -ENODEV` degraded special case, which 5.3 deliberately
* replaced. In production the commit engine is wired at cluster init, so a
* single-member primary (n_peers=1) self-commits: persist_wal_sync_append
* returns 0, sync status is 0 (NOT -ENODEV), and committed_lsn advances to
* the entry. The engine — not persist — owns the advancement.
*
* This test documents the actual behaviour so regressions are caught if the
* single-node path is accidentally changed to advance committed_lsn.
* A priming async append makes the sync entry land at LSN 1: the first-ever
* WAL entry is LSN 0, which the adapter routes to the legacy branch (the
* accepted once-per-cluster-lifetime LSN-0 interim behaviour).
*/
static void test_single_node_committed_lsn_unchanged(void)
static void test_single_node_committed_lsn_advances(void)
{
commit_engine_t ce;
persist_t* p = make_p2_persist("clsn_1n");
TEST_ASSERT_NOT_NULL(p);
uint64_t committed_before =
atomic_load_explicit(&p->committed_lsn, memory_order_acquire);
/* Wire the engine exactly as cluster_init_phase2 does for n_peers=1 */
TEST_ASSERT_EQUAL_INT(0, commit_engine_init(&ce, p, 1, 8));
repl_set_commit_engine(&p->repl, &ce);
/* Priming append: sync entry below lands at LSN 1 (engine-wired path) */
TEST_ASSERT_EQUAL_INT(0, persist_wal_async(p, WAL_JOB_UPDATE, "p", 1));
int sync_rc = 0;
int sync_rc = -1;
int rc = persist_wal_sync_append(p, WAL_JOB_SUBMIT, "job", 3, &sync_rc);
/* WAL write must succeed locally */
TEST_ASSERT_EQUAL_INT(0, rc);
/* Degraded mode: no standbys → -ENODEV */
TEST_ASSERT_EQUAL_INT(-ENODEV, sync_rc);
/* committed_lsn must remain at its pre-call value (not advanced) */
uint64_t committed_after =
atomic_load_explicit(&p->committed_lsn, memory_order_acquire);
TEST_ASSERT_EQUAL_UINT64_MESSAGE(committed_before, committed_after,
"single-node: committed_lsn must stay unchanged (degraded, no standbys)");
/* Single-member self-quorum: confirmed locally, no -ENODEV degraded mode */
TEST_ASSERT_EQUAL_INT_MESSAGE(0, sync_rc,
"single-node wired engine must self-commit (0), not -ENODEV");
/* committed_lsn advances to the entry (engine-owned advancement) */
TEST_ASSERT_EQUAL_UINT64_MESSAGE(
1, atomic_load_explicit(&p->committed_lsn, memory_order_acquire),
"single-node: committed_lsn must advance to the self-committed entry");
/* WAL LSN did advance (local write succeeded) */
TEST_ASSERT_TRUE(p->current_lsn > 0);
commit_engine_fini(&ce);
teardown_p2_persist(p);
printf("test_single_node_committed_lsn_unchanged: PASS\n");
printf("test_single_node_committed_lsn_advances: PASS\n");
}
/* ------------------------------------------------------------------ */
@ -407,7 +414,7 @@ void test_p2_integration(void)
RUN_TEST(test_trim_lsn_with_active_standby_at_lsn_5);
RUN_TEST(test_checkpoint_does_not_update_replicated_crc);
RUN_TEST(test_sync_replication_degraded_mode);
RUN_TEST(test_single_node_committed_lsn_unchanged);
RUN_TEST(test_single_node_committed_lsn_advances);
RUN_TEST(test_catchup_full_lifecycle_integration);
RUN_TEST(test_needs_snapshot_boundary);
RUN_TEST(test_repl_api_completeness);