feat(ha-commit-durability): add XCE_TESTING durable-LSN-on-disk verification seam (task 1.2)

This commit is contained in:
Rongsong Shen 2026-06-18 04:56:50 +00:00
parent 1ca2d640cb
commit f90395282c
6 changed files with 357 additions and 1 deletions

View file

@ -59,6 +59,7 @@ extern void test_egress_filter(void);
extern void test_demotion_convergence(void);
extern void test_convergence_verification(void);
extern void test_coldstart_lsn0(void);
extern void test_commit_durability(void);
void setUp(void) { return; }
@ -187,6 +188,7 @@ int main(int argc, char* argv[])
TEST(test_demotion_convergence);
TEST(test_convergence_verification);
TEST(test_coldstart_lsn0);
TEST(test_commit_durability);
return UNITY_END();
}

View file

@ -0,0 +1,189 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <unity/unity.h>
#include "config.h"
#include "persist.h"
/* ------------------------------------------------------------------ *
* ha-commit-durability — task 1.2: durability-verification test seam *
* *
* These tests exercise persist_test_durable_lsn_on_disk(), the *
* XCE_TESTING-only seam that re-reads + CRC-validates the WAL from *
* stable storage and reports the highest fully-persisted LSN — *
* independent of the in-memory current_lsn watermark. This lets a *
* test assert an acknowledged operation is recoverable from disk after *
* an immediate ungraceful termination, without relying on the *
* background flush timer (Req 3.2). The seam is purely inspective: *
* it changes nothing observable about the acknowledgment contract *
* (Req 3.3). *
* *
* The seam is compiled ONLY into the controller_unit_test target *
* (-DXCE_TESTING). The production xce_controller target links without *
* it — see #ifdef XCE_TESTING in persist.c / declaration in persist.h. *
* ------------------------------------------------------------------ */
#ifdef XCE_TESTING
/* Build a persist instance with a deliberately slow background flush
* cadence, so the only durability point is an explicit persist_flush()
* (mirrors make_fresh_persist in test_persist_recovery.c). */
static persist_t* make_durability_persist(const char* tmpdir)
{
persist_t* p = calloc(1, sizeof(persist_t));
if (!p)
return NULL;
persist_config_t cfg = persist_config_defaults();
cfg.wal_sync_ms = 5000; /* slow background fsync — durability comes from
* the explicit flush, not the timer */
cfg.snap_keep_n = 2;
persist_set_workdir(p, tmpdir, "ctrl-test");
if (persist_init(p, &cfg, NULL) != 0) {
free(p);
return NULL;
}
if (persist_start(p) != 0) {
persist_fini(p);
free(p);
return NULL;
}
return p;
}
static void teardown_durability_persist(persist_t* p)
{
persist_stop(p);
persist_fini(p);
free(p);
}
/* ------------------------------------------------------------------ *
* Test 1: an empty WAL has no durable entries (floor == 0). *
* An empty first-boot WAL is not corruption — it simply has nothing *
* persisted, so the seam reports 0. *
* ------------------------------------------------------------------ */
static void test_durable_floor_empty(void)
{
char tmp[256];
strcpy(tmp, "/tmp/xce_durfloor0_XXXXXX");
TEST_ASSERT_NOT_NULL(mkdtemp(tmp));
persist_t* p = make_durability_persist(tmp);
TEST_ASSERT_NOT_NULL(p);
uint64_t floor = persist_test_durable_lsn_on_disk(p);
TEST_ASSERT_EQUAL_UINT64(0, floor);
teardown_durability_persist(p);
printf("test_durable_floor_empty: PASS\n");
}
/* ------------------------------------------------------------------ *
* Test 2 (core Req 3.2): after append + explicit fdatasync, the seam *
* sees the entries on disk — the durable floor equals the highest *
* appended LSN, recoverable independent of in-memory state. *
* ------------------------------------------------------------------ */
static void test_durable_floor_after_flush(void)
{
char tmp[256];
strcpy(tmp, "/tmp/xce_durfloor1_XXXXXX");
TEST_ASSERT_NOT_NULL(mkdtemp(tmp));
persist_t* p = make_durability_persist(tmp);
TEST_ASSERT_NOT_NULL(p);
/* Append three entries. LSN 0 is reserved, so these take LSN 1,2,3.
* current_lsn advances to 4 (one past the last appended). */
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));
/* Durability point: fdatasync the segment so the bytes are on stable
* storage (this is what an acknowledged-before-durable contract must
* have done before replying ok). */
TEST_ASSERT_EQUAL_INT(0, persist_flush(p));
/* The seam re-reads from disk and reports the highest intact LSN. */
uint64_t floor = persist_test_durable_lsn_on_disk(p);
TEST_ASSERT_EQUAL_UINT64(p->current_lsn - 1, floor);
TEST_ASSERT_EQUAL_UINT64(3, floor);
teardown_durability_persist(p);
printf("test_durable_floor_after_flush: PASS\n");
}
/* ------------------------------------------------------------------ *
* Test 3 (recoverable-after-ungraceful-kill, Req 3.2): a SECOND *
* persist instance opened over the same WAL dir — with NO in-memory *
* state from the writer — recovers exactly the durable floor the seam *
* reported. This is the "survives an immediate ungraceful *
* termination" assertion: durability lives on disk, not in the *
* volatile buffers of the process that wrote it. *
* ------------------------------------------------------------------ */
static void test_durable_floor_survives_process_loss(void)
{
char tmp[256];
strcpy(tmp, "/tmp/xce_durfloor2_XXXXXX");
TEST_ASSERT_NOT_NULL(mkdtemp(tmp));
uint64_t durable_floor = 0;
/* Writer process: append, fdatasync, then "die" (we drop the instance
* without any further flush — the bytes are already durable). */
{
persist_t* p = make_durability_persist(tmp);
TEST_ASSERT_NOT_NULL(p);
TEST_ASSERT_EQUAL_INT(0, persist_wal_async(p, WAL_JOB_UPDATE, "x", 1));
TEST_ASSERT_EQUAL_INT(0, persist_wal_async(p, WAL_JOB_UPDATE, "y", 1));
TEST_ASSERT_EQUAL_INT(0, persist_flush(p));
durable_floor = persist_test_durable_lsn_on_disk(p);
TEST_ASSERT_EQUAL_UINT64(2, durable_floor);
teardown_durability_persist(p);
}
/* "Restarted" process: a fresh persist_t with zero in-memory state,
* opened over the same on-disk WAL. Its durable floor — read straight
* from disk before any recover/replay — matches what the writer
* persisted. Acknowledged-before-durable ops therefore survive the
* loss of the acking process. */
{
persist_t* p2 = make_durability_persist(tmp);
TEST_ASSERT_NOT_NULL(p2);
/* current_lsn is still the fresh-boot value (1, LSN 0 reserved):
* the durable floor does NOT come from in-memory state. */
uint64_t floor2 = persist_test_durable_lsn_on_disk(p2);
TEST_ASSERT_EQUAL_UINT64(durable_floor, floor2);
TEST_ASSERT_EQUAL_UINT64(2, floor2);
teardown_durability_persist(p2);
}
printf("test_durable_floor_survives_process_loss: PASS\n");
}
#endif /* XCE_TESTING */
/* ------------------------------------------------------------------ */
/* Test suite entry point */
/* ------------------------------------------------------------------ */
void test_commit_durability(void)
{
#ifdef XCE_TESTING
RUN_TEST(test_durable_floor_empty);
RUN_TEST(test_durable_floor_after_flush);
RUN_TEST(test_durable_floor_survives_process_loss);
#else
printf("test_commit_durability: skipped (no XCE_TESTING)\n");
#endif
}