feat(ha-term-safety): on_get_entry_term + on_truncate_from callback bodies (task 2.2)
on_get_entry_term → persist_entry_term (recorded term, 0=absent, legacy-0→1; caveat documented per 1.3 review). on_truncate_from → persist_truncate_wal_above(p, index-1) (delete LSN>=index), returns rc for library/leader retry; non-promotable fence intact; replaces the committed-boundary heuristic on the library-driven path. New truncate_term_index_locked keeps the in-memory per-LSN term index consistent on both truncate paths (under lock, cutoff-aligned) → no stale-term read after truncation. 3 tests, reviewer APPROVED, per-group 0 failures.
This commit is contained in:
parent
8f3d3ce8f4
commit
ed2443533f
5 changed files with 210 additions and 5 deletions
|
|
@ -378,6 +378,122 @@ static void test_on_persist_entry_failure_propagates_no_ack(void)
|
|||
printf("test_on_persist_entry_failure_propagates_no_ack: PASS\n");
|
||||
}
|
||||
|
||||
/* ================================================================== */
|
||||
/* Task 2.2 — on_get_entry_term + on_truncate_from callback bodies */
|
||||
/* ================================================================== */
|
||||
|
||||
/* Persist a couple of replicated entries (each at a 1-based index == LSN)
|
||||
* through the WIRED on_persist_entry callback, stamping a leader term per
|
||||
* entry. Returns the (started) cluster the caller tears down. */
|
||||
static cluster_t* persist_two_entries(const char* tmp, uint64_t t1, uint64_t t2)
|
||||
{
|
||||
cluster_t* c = make_cluster(tmp);
|
||||
if (!c) return NULL;
|
||||
atomic_store(&c->ha.role, CTRL_STANDBY);
|
||||
|
||||
uint8_t blob[64];
|
||||
size_t n1 = pack_entry(blob, WAL_JOB_UPDATE, "a", 1);
|
||||
if (ha_cb_persist_entry_for_test(&c->ha, /*index*/1, t1, blob, n1) != 0) {
|
||||
teardown_cluster(c);
|
||||
return NULL;
|
||||
}
|
||||
size_t n2 = pack_entry(blob, WAL_JOB_UPDATE, "b", 1);
|
||||
if (ha_cb_persist_entry_for_test(&c->ha, /*index*/2, t2, blob, n2) != 0) {
|
||||
teardown_cluster(c);
|
||||
return NULL;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
/* Test: on_get_entry_term returns the stamped per-LSN term for present entries,
|
||||
* the lowest-known term for a legacy-0 entry, and 0 for an absent LSN
|
||||
* (R1.5, R3.1). */
|
||||
static void test_on_get_entry_term_present_legacy_absent(void)
|
||||
{
|
||||
char tmp[256];
|
||||
strcpy(tmp, "/tmp/xce_ts_get_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(tmp));
|
||||
|
||||
cluster_t* c = make_cluster(tmp);
|
||||
TEST_ASSERT_NOT_NULL(c);
|
||||
atomic_store(&c->ha.role, CTRL_STANDBY);
|
||||
|
||||
uint8_t blob[64];
|
||||
|
||||
/* LSN 1: real term 6. */
|
||||
size_t n = pack_entry(blob, WAL_JOB_UPDATE, "r", 1);
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
ha_cb_persist_entry_for_test(&c->ha, /*index*/1, /*term*/6, blob, n));
|
||||
|
||||
/* LSN 2: legacy-0 entry (leader term 0) → reads back as WAL_LEGACY_TERM. */
|
||||
n = pack_entry(blob, WAL_JOB_UPDATE, "l", 1);
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
ha_cb_persist_entry_for_test(&c->ha, /*index*/2, /*term*/0, blob, n));
|
||||
|
||||
/* Present real-term entry returns its term verbatim. */
|
||||
TEST_ASSERT_EQUAL_UINT64(6, ha_cb_get_entry_term_for_test(&c->ha, 1));
|
||||
/* Present legacy-0 entry maps to the lowest-known real term. */
|
||||
TEST_ASSERT_EQUAL_UINT64(LEGACY_LOWEST_TERM,
|
||||
ha_cb_get_entry_term_for_test(&c->ha, 2));
|
||||
/* Absent LSNs (0 reserved, 3 never appended) return 0. */
|
||||
TEST_ASSERT_EQUAL_UINT64(0, ha_cb_get_entry_term_for_test(&c->ha, 0));
|
||||
TEST_ASSERT_EQUAL_UINT64(0, ha_cb_get_entry_term_for_test(&c->ha, 3));
|
||||
|
||||
teardown_cluster(c);
|
||||
printf("test_on_get_entry_term_present_legacy_absent: PASS\n");
|
||||
}
|
||||
|
||||
/* Test: on_truncate_from(k) deletes local entries at LSN >= k; the prefix < k
|
||||
* stays intact and on_get_entry_term returns 0 for the truncated LSNs while the
|
||||
* term index shrinks consistently (R1.8, R3.1, R3.2). */
|
||||
static void test_on_truncate_from_drops_suffix(void)
|
||||
{
|
||||
char tmp[256];
|
||||
strcpy(tmp, "/tmp/xce_ts_trunc_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(tmp));
|
||||
|
||||
/* Persist LSN 1 (term 3) and LSN 2 (term 5). */
|
||||
cluster_t* c = persist_two_entries(tmp, /*t1*/3, /*t2*/5);
|
||||
TEST_ASSERT_NOT_NULL(c);
|
||||
|
||||
/* Both present before truncation. */
|
||||
TEST_ASSERT_EQUAL_UINT64(3, ha_cb_get_entry_term_for_test(&c->ha, 1));
|
||||
TEST_ASSERT_EQUAL_UINT64(5, ha_cb_get_entry_term_for_test(&c->ha, 2));
|
||||
TEST_ASSERT_EQUAL_UINT64(3, c->persist.current_lsn);
|
||||
|
||||
/* Truncate from LSN 2 → entries at LSN >= 2 are deleted. */
|
||||
TEST_ASSERT_EQUAL_INT(0, ha_cb_truncate_from_for_test(&c->ha, 2));
|
||||
|
||||
/* Prefix < 2 intact, suffix >= 2 gone (term index returns 0). */
|
||||
TEST_ASSERT_EQUAL_UINT64(3, ha_cb_get_entry_term_for_test(&c->ha, 1));
|
||||
TEST_ASSERT_EQUAL_UINT64(0, ha_cb_get_entry_term_for_test(&c->ha, 2));
|
||||
TEST_ASSERT_EQUAL_UINT64(2, c->persist.current_lsn);
|
||||
|
||||
teardown_cluster(c);
|
||||
printf("test_on_truncate_from_drops_suffix: PASS\n");
|
||||
}
|
||||
|
||||
/* Test: on_truncate_from(1) deletes the whole log (LSN >= 1); every entry's
|
||||
* term reads back absent and the tip collapses to LSN 1. */
|
||||
static void test_on_truncate_from_removes_all(void)
|
||||
{
|
||||
char tmp[256];
|
||||
strcpy(tmp, "/tmp/xce_ts_trunc_all_XXXXXX");
|
||||
TEST_ASSERT_NOT_NULL(mkdtemp(tmp));
|
||||
|
||||
cluster_t* c = persist_two_entries(tmp, /*t1*/2, /*t2*/2);
|
||||
TEST_ASSERT_NOT_NULL(c);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, ha_cb_truncate_from_for_test(&c->ha, 1));
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT64(0, ha_cb_get_entry_term_for_test(&c->ha, 1));
|
||||
TEST_ASSERT_EQUAL_UINT64(0, ha_cb_get_entry_term_for_test(&c->ha, 2));
|
||||
TEST_ASSERT_EQUAL_UINT64(1, c->persist.current_lsn);
|
||||
|
||||
teardown_cluster(c);
|
||||
printf("test_on_truncate_from_removes_all: PASS\n");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Suite entry point */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
@ -392,4 +508,7 @@ void test_term_safety_adapter(void)
|
|||
RUN_TEST(test_on_persist_entry_stamps_term_standby);
|
||||
RUN_TEST(test_on_persist_entry_stamps_term_primary);
|
||||
RUN_TEST(test_on_persist_entry_failure_propagates_no_ack);
|
||||
RUN_TEST(test_on_get_entry_term_present_legacy_absent);
|
||||
RUN_TEST(test_on_truncate_from_drops_suffix);
|
||||
RUN_TEST(test_on_truncate_from_removes_all);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue