feat(ha-commit-durability): cap commit self-contribution at durable_lsn + durable-advance commit wake (task 2.2)

This commit is contained in:
Rongsong Shen 2026-06-18 06:29:47 +00:00
parent cc83caf5a0
commit c41edc515e
10 changed files with 395 additions and 13 deletions

View file

@ -8,6 +8,7 @@
#include <unity/unity.h>
#include "commit.h"
#include "config.h"
#include "persist.h"
@ -433,6 +434,69 @@ static void test_durable_lsn_holds_on_fsync_failure(void)
printf("test_durable_lsn_holds_on_fsync_failure: PASS\n");
}
/* ================================================================== *
* task 2.2 — durable-advance wake drives commit (integration) *
* *
* End-to-end over a REAL persist_t + commit_engine_t (single-member, *
* NULL cluster): the group-commit flusher advances durable_lsn and *
* fires the registered durable-advance callback, which the commit *
* engine uses to advance committed_lsn and wake WAIT callers. Proves *
* the persist↔commit wiring works against the real flusher thread — *
* not just the directly-driven callback (covered in *
* test_commit_engine.c). n_peers=1 means self alone is the majority, *
* so committed_lsn tracks durable_lsn with no standby ACKs needed. *
* ------------------------------------------------------------------ */
static void test_durable_advance_drives_commit_single_member(void)
{
char tmp[256];
strcpy(tmp, "/tmp/xce_durcommit_XXXXXX");
TEST_ASSERT_NOT_NULL(mkdtemp(tmp));
persist_t* p = make_durability_persist(tmp);
TEST_ASSERT_NOT_NULL(p);
/* Single-member commit engine over the real persist. n_vcpus large enough
* to admit a wait. commit_engine_init registers the durable-advance cb. */
commit_engine_t ce;
TEST_ASSERT_EQUAL_INT(0, commit_engine_init(&ce, p, 1, 8));
/* committed starts at 0; nothing appended/durable. */
TEST_ASSERT_EQUAL_UINT64(0, atomic_load(&p->committed_lsn));
/* Append three entries (LSN 1,2,3). They are in page cache only — the slow
* 5s cadence means the append-SIGNAL (not the timer) must drive the flush;
* the real flusher fdatasyncs, advances durable_lsn, and fires the cb,
* which advances committed_lsn for this single-member quorum. */
TEST_ASSERT_EQUAL_INT(0, persist_wal_async(p, WAL_JOB_UPDATE, "a", 1));
TEST_ASSERT_EQUAL_INT(0, persist_wal_async(p, WAL_JOB_UPDATE, "b", 1));
TEST_ASSERT_EQUAL_INT(0, persist_wal_async(p, WAL_JOB_UPDATE, "c", 1));
uint64_t tip = p->current_lsn - 1; /* 3 */
TEST_ASSERT_EQUAL_UINT64(3, tip);
/* Wait for the flusher to make the tip durable AND for the wake-driven
* commit to catch up. committed_lsn must reach the tip — driven by the
* durable-advance callback, not by any ACK (there are no standbys). */
uint64_t committed = 0;
for (int i = 0; i < 5000; i++) {
committed = atomic_load(&p->committed_lsn);
if (committed >= tip)
break;
usleep(1000);
}
TEST_ASSERT_EQUAL_UINT64_MESSAGE(
tip, committed,
"durable-advance callback must drive committed_lsn to the durable tip");
/* committed never exceeds durable (the invariant the wake preserves). */
TEST_ASSERT_TRUE(atomic_load(&p->committed_lsn) <= persist_durable_lsn(p));
/* Tear the engine down BEFORE the persist (unhooks the callback so the
* flusher cannot fire against a destroyed engine). */
commit_engine_fini(&ce);
teardown_durability_persist(p);
printf("test_durable_advance_drives_commit_single_member: PASS\n");
}
#endif /* XCE_TESTING */
/* ------------------------------------------------------------------ */
@ -449,6 +513,7 @@ void test_commit_durability(void)
RUN_TEST(test_durable_lsn_never_exceeds_synced_tip);
RUN_TEST(test_durable_lsn_invariants_under_load);
RUN_TEST(test_durable_lsn_holds_on_fsync_failure);
RUN_TEST(test_durable_advance_drives_commit_single_member);
#else
printf("test_commit_durability: skipped (no XCE_TESTING)\n");
#endif