feat(ha-repl-hardening): add batch WAL catch-up on standby side (task 2.2)
Extend ha_apply_wal_catchup_entry with batch branch: - Check n_entries > 0, iterate entries[] list in order - Apply each via apply_wal_entry(); skip NULL/errors without crash - Advance applied_lsn once after full batch (not per-entry) - n_entries == 0 falls through to task 2.1 single-entry path 4 new unit tests covering full batch, partial batch, error skip, fallback
This commit is contained in:
parent
647b88ce57
commit
6bbcff62ca
3 changed files with 295 additions and 41 deletions
|
|
@ -1045,6 +1045,199 @@ static void test_wal_catchup_entry_null_entry_no_crash(void)
|
|||
printf("test_wal_catchup_entry_null_entry_no_crash: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Task 2.2: batch WAL catch-up entries on standby ─────────────────────── */
|
||||
|
||||
/* Test 2.2-A: batch of 3 entries — applied_lsn advances to last+1.
|
||||
* Requirements 5.3, 5.5: apply each in order, advance after full batch. */
|
||||
static void test_wal_catchup_batch_advances_applied_lsn(void)
|
||||
{
|
||||
char tmpdir[256];
|
||||
persist_t *p = make_test_persist_catchup(tmpdir, sizeof(tmpdir));
|
||||
|
||||
ha_state_t ha;
|
||||
memset(&ha, 0, sizeof(ha));
|
||||
ha_config_t cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.heartbeat_timeout_ms = 10000;
|
||||
cfg.election_timeout_ms = 1500;
|
||||
cfg.committing_timeout_ms = 10000;
|
||||
ha_init(&ha, &cfg, 0);
|
||||
|
||||
/* applied_lsn starts at 0 */
|
||||
TEST_ASSERT_EQUAL_UINT64(0,
|
||||
(uint64_t)atomic_load_explicit(&ha.applied_lsn, memory_order_acquire));
|
||||
|
||||
/* Build 3 sub-entries at LSNs 10, 11, 12 */
|
||||
wal_catchup_entry_t e0, e1, e2;
|
||||
memset(&e0, 0, sizeof(e0));
|
||||
memset(&e1, 0, sizeof(e1));
|
||||
memset(&e2, 0, sizeof(e2));
|
||||
e0.lsn = 10; e0.entry_type = (uint8_t)WAL_JOB_UPDATE;
|
||||
e1.lsn = 11; e1.entry_type = (uint8_t)WAL_JOB_UPDATE;
|
||||
e2.lsn = 12; e2.entry_type = (uint8_t)WAL_JOB_UPDATE;
|
||||
|
||||
wal_catchup_entry_t *batch_entries[3] = { &e0, &e1, &e2 };
|
||||
|
||||
/* Outer entry carries the batch; lsn/entry_type ignored when n_entries > 0 */
|
||||
wal_catchup_entry_t outer;
|
||||
memset(&outer, 0, sizeof(outer));
|
||||
outer.n_entries = 3;
|
||||
outer.entries = batch_entries;
|
||||
|
||||
int rc = ha_apply_wal_catchup_entry(&ha, p, &outer);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
/* applied_lsn must advance to last_entry->lsn + 1 = 13 */
|
||||
TEST_ASSERT_EQUAL_UINT64(13,
|
||||
(uint64_t)atomic_load_explicit(&ha.applied_lsn, memory_order_acquire));
|
||||
|
||||
ha_fini(&ha);
|
||||
destroy_test_persist_catchup(p);
|
||||
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", tmpdir);
|
||||
system(cmd);
|
||||
printf("test_wal_catchup_batch_advances_applied_lsn: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 2.2-B: partial batch (2 of max 3) — applied_lsn advances correctly.
|
||||
* Requirement 5.4: partial batch (fewer than max) sent and applied without wait. */
|
||||
static void test_wal_catchup_partial_batch(void)
|
||||
{
|
||||
char tmpdir[256];
|
||||
persist_t *p = make_test_persist_catchup(tmpdir, sizeof(tmpdir));
|
||||
|
||||
ha_state_t ha;
|
||||
memset(&ha, 0, sizeof(ha));
|
||||
ha_config_t cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.heartbeat_timeout_ms = 10000;
|
||||
cfg.election_timeout_ms = 1500;
|
||||
cfg.committing_timeout_ms = 10000;
|
||||
ha_init(&ha, &cfg, 0);
|
||||
|
||||
/* Pre-set applied_lsn to 5 to verify monotonic advance */
|
||||
atomic_store_explicit(&ha.applied_lsn, 5, memory_order_release);
|
||||
|
||||
wal_catchup_entry_t e0, e1;
|
||||
memset(&e0, 0, sizeof(e0));
|
||||
memset(&e1, 0, sizeof(e1));
|
||||
e0.lsn = 20; e0.entry_type = (uint8_t)WAL_JOB_UPDATE;
|
||||
e1.lsn = 21; e1.entry_type = (uint8_t)WAL_JOB_UPDATE;
|
||||
|
||||
wal_catchup_entry_t *batch_entries[2] = { &e0, &e1 };
|
||||
|
||||
wal_catchup_entry_t outer;
|
||||
memset(&outer, 0, sizeof(outer));
|
||||
outer.n_entries = 2;
|
||||
outer.entries = batch_entries;
|
||||
|
||||
int rc = ha_apply_wal_catchup_entry(&ha, p, &outer);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
/* applied_lsn must advance to 21+1=22 */
|
||||
TEST_ASSERT_EQUAL_UINT64(22,
|
||||
(uint64_t)atomic_load_explicit(&ha.applied_lsn, memory_order_acquire));
|
||||
|
||||
ha_fini(&ha);
|
||||
destroy_test_persist_catchup(p);
|
||||
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", tmpdir);
|
||||
system(cmd);
|
||||
printf("test_wal_catchup_partial_batch: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 2.2-C: batch with one error entry — error is logged and skipped,
|
||||
* remaining entries are still applied, applied_lsn advances for the good ones.
|
||||
* Requirement 3.3/5.5: error in batch → skip that entry, continue with rest. */
|
||||
static void test_wal_catchup_batch_error_entry_skipped(void)
|
||||
{
|
||||
char tmpdir[256];
|
||||
persist_t *p = make_test_persist_catchup(tmpdir, sizeof(tmpdir));
|
||||
|
||||
ha_state_t ha;
|
||||
memset(&ha, 0, sizeof(ha));
|
||||
ha_config_t cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.heartbeat_timeout_ms = 10000;
|
||||
cfg.election_timeout_ms = 1500;
|
||||
cfg.committing_timeout_ms = 10000;
|
||||
ha_init(&ha, &cfg, 0);
|
||||
|
||||
/* Good entry at LSN 30, then a NULL pointer (simulates corrupt entry),
|
||||
* then good entry at LSN 32. */
|
||||
wal_catchup_entry_t e0, e2;
|
||||
memset(&e0, 0, sizeof(e0));
|
||||
memset(&e2, 0, sizeof(e2));
|
||||
e0.lsn = 30; e0.entry_type = (uint8_t)WAL_JOB_UPDATE;
|
||||
e2.lsn = 32; e2.entry_type = (uint8_t)WAL_JOB_UPDATE;
|
||||
|
||||
/* NULL in the middle represents a missing/corrupt sub-entry */
|
||||
wal_catchup_entry_t *batch_entries[3] = { &e0, NULL, &e2 };
|
||||
|
||||
wal_catchup_entry_t outer;
|
||||
memset(&outer, 0, sizeof(outer));
|
||||
outer.n_entries = 3;
|
||||
outer.entries = batch_entries;
|
||||
|
||||
/* Handler must not crash; applied_lsn advances for successful entries */
|
||||
int rc = ha_apply_wal_catchup_entry(&ha, p, &outer);
|
||||
/* Overall rc == 0: outer batch call succeeds even with inner skips */
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
/* applied_lsn must reach at least 31 (e0 applied); ideally 33 (e2 applied) */
|
||||
uint64_t final_lsn = (uint64_t)atomic_load_explicit(&ha.applied_lsn,
|
||||
memory_order_acquire);
|
||||
TEST_ASSERT_TRUE(final_lsn >= 31);
|
||||
|
||||
ha_fini(&ha);
|
||||
destroy_test_persist_catchup(p);
|
||||
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", tmpdir);
|
||||
system(cmd);
|
||||
printf("test_wal_catchup_batch_error_entry_skipped: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 2.2-D: n_entries==0 falls through to single-entry path (backward compat) */
|
||||
static void test_wal_catchup_batch_zero_entries_fallback(void)
|
||||
{
|
||||
char tmpdir[256];
|
||||
persist_t *p = make_test_persist_catchup(tmpdir, sizeof(tmpdir));
|
||||
|
||||
ha_state_t ha;
|
||||
memset(&ha, 0, sizeof(ha));
|
||||
ha_config_t cfg;
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.heartbeat_timeout_ms = 10000;
|
||||
cfg.election_timeout_ms = 1500;
|
||||
cfg.committing_timeout_ms = 10000;
|
||||
ha_init(&ha, &cfg, 0);
|
||||
|
||||
/* n_entries == 0 → single-entry path from task 2.1 */
|
||||
wal_catchup_entry_t entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
entry.lsn = 50;
|
||||
entry.entry_type = (uint8_t)WAL_JOB_UPDATE;
|
||||
entry.n_entries = 0;
|
||||
entry.entries = NULL;
|
||||
|
||||
int rc = ha_apply_wal_catchup_entry(&ha, p, &entry);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
/* Single-entry path: applied_lsn = 50+1 = 51 */
|
||||
TEST_ASSERT_EQUAL_UINT64(51,
|
||||
(uint64_t)atomic_load_explicit(&ha.applied_lsn, memory_order_acquire));
|
||||
|
||||
ha_fini(&ha);
|
||||
destroy_test_persist_catchup(p);
|
||||
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", tmpdir);
|
||||
system(cmd);
|
||||
printf("test_wal_catchup_batch_zero_entries_fallback: PASS\n");
|
||||
}
|
||||
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/* ── dispatch ───────────────────────────────────── */
|
||||
|
|
@ -1077,6 +1270,11 @@ void test_config_repl(void)
|
|||
RUN_TEST(test_wal_catchup_entry_advances_applied_lsn);
|
||||
RUN_TEST(test_wal_catchup_entry_error_skipped_no_crash);
|
||||
RUN_TEST(test_wal_catchup_entry_null_entry_no_crash);
|
||||
/* Task 2.2: batch WAL catch-up entries */
|
||||
RUN_TEST(test_wal_catchup_batch_advances_applied_lsn);
|
||||
RUN_TEST(test_wal_catchup_partial_batch);
|
||||
RUN_TEST(test_wal_catchup_batch_error_entry_skipped);
|
||||
RUN_TEST(test_wal_catchup_batch_zero_entries_fallback);
|
||||
#endif
|
||||
/* Task A: raw capnp shims */
|
||||
RUN_TEST(test_replconfig_req_ssl_encode);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue