feat(ha-repl-hardening): batch WAL streaming from decision loop (task 3.4)
ha_stream_wal_to_standby() reads WAL from committed_lsn range, accumulates entries to 256/64KB per batch, enqueues all via xce_request() (non-blocking). Partial final batch sent immediately. Wired into STREAM_WAL case. 3 tests: 500-entries/2-batches, partial batch, empty range. Fixed stale task-number comments per review.
This commit is contained in:
parent
d1c7872d73
commit
6b69a47c50
7 changed files with 469 additions and 20 deletions
|
|
@ -1329,6 +1329,183 @@ static void test_nop_reply_zero_state_fresh_standby(void)
|
|||
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/* ── Task 3.4: ha_stream_wal_to_standby batch WAL streaming ──────────────── */
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
|
||||
/*
|
||||
* xce_wal_stream_request_hook: test-only seam declared in config_repl.c.
|
||||
* When non-NULL, ha_stream_wal_to_standby() calls this INSTEAD OF xce_request()
|
||||
* for each batch message, allowing tests to inspect and count batches without
|
||||
* a real endpoint.
|
||||
*
|
||||
* Signature: int hook(endpoint_t *ep, xce_request_t *req)
|
||||
* ep — outbound endpoint (may be NULL in tests)
|
||||
* req — the batch OPCode_walCatchupEntry request being sent
|
||||
* Returns 0 on success (the hook owns no resources in the req).
|
||||
*/
|
||||
extern int (*xce_wal_stream_request_hook)(endpoint_t *ep, xce_request_t *req);
|
||||
|
||||
/* Helper: write N minimal WAL entries into a single segment file in wal_dir.
|
||||
* Entries have consecutive LSNs starting at start_lsn.
|
||||
* Returns 0 on success, -1 on error. */
|
||||
static int write_test_wal_segment(const char *wal_dir, uint64_t start_lsn,
|
||||
int n_entries)
|
||||
{
|
||||
char seg_path[4096];
|
||||
snprintf(seg_path, sizeof(seg_path),
|
||||
"%s/seg-%016" PRIx64 ".wal", wal_dir, start_lsn);
|
||||
|
||||
int fd = open(seg_path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
|
||||
if (fd < 0) return -1;
|
||||
|
||||
for (int i = 0; i < n_entries; i++) {
|
||||
wal_entry_hdr_t hdr;
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
hdr.lsn = start_lsn + (uint64_t)i;
|
||||
hdr.type = WAL_JOB_UPDATE;
|
||||
hdr.payload_len = 0;
|
||||
hdr.term = 1;
|
||||
ssize_t nw = write(fd, &hdr, sizeof(hdr));
|
||||
if (nw != (ssize_t)sizeof(hdr)) { close(fd); return -1; }
|
||||
}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Batch-counting state for the hook */
|
||||
static int g_wal_stream_batch_count = 0;
|
||||
static int g_wal_stream_total_entries = 0;
|
||||
static int g_wal_stream_last_n_entries = 0;
|
||||
|
||||
static int counting_wal_stream_hook(endpoint_t *ep, xce_request_t *req)
|
||||
{
|
||||
(void)ep;
|
||||
if (!req || !req->body.walcatchupentrymsg) return 0;
|
||||
wal_catchup_entry_t *batch = (wal_catchup_entry_t *)req->body.walcatchupentrymsg;
|
||||
g_wal_stream_batch_count++;
|
||||
g_wal_stream_last_n_entries = batch->n_entries;
|
||||
g_wal_stream_total_entries += batch->n_entries;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test 3.4-A: streaming 500 entries produces exactly 2 batches
|
||||
* (batch 1: 256 entries, batch 2: 244 entries).
|
||||
* Requirement 5.2: accumulate up to 256 entries per batch.
|
||||
* Requirement 5.4: partial final batch is sent immediately.
|
||||
*/
|
||||
static void test_wal_stream_500_entries_two_batches(void)
|
||||
{
|
||||
/* Set up WAL directory with 500 consecutive entries starting at LSN 1 */
|
||||
char tmpdir[256];
|
||||
snprintf(tmpdir, sizeof(tmpdir), "/tmp/xce_wal_stream_%d_%ld",
|
||||
(int)getpid(), (long)time(NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, mkdir(tmpdir, 0700));
|
||||
|
||||
/* Write all 500 entries in a single segment starting at LSN 1 */
|
||||
int rc = write_test_wal_segment(tmpdir, /*start_lsn=*/1, /*n_entries=*/500);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to write test WAL segment");
|
||||
|
||||
/* Install counting hook */
|
||||
xce_wal_stream_request_hook = counting_wal_stream_hook;
|
||||
g_wal_stream_batch_count = 0;
|
||||
g_wal_stream_total_entries = 0;
|
||||
g_wal_stream_last_n_entries = 0;
|
||||
|
||||
/* Stream entries [1..500]: standby_lsn=0, committed_lsn=500 */
|
||||
rc = ha_stream_wal_to_standby(/*ep=*/NULL, tmpdir,
|
||||
/*standby_lsn=*/0,
|
||||
/*committed_lsn=*/500);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "ha_stream_wal_to_standby failed");
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(2, g_wal_stream_batch_count,
|
||||
"500 entries must produce exactly 2 batches (256+244)");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(500, g_wal_stream_total_entries,
|
||||
"All 500 entries must be sent");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(244, g_wal_stream_last_n_entries,
|
||||
"Final batch must contain 244 entries (500 - 256)");
|
||||
|
||||
xce_wal_stream_request_hook = NULL;
|
||||
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", tmpdir);
|
||||
system(cmd);
|
||||
printf("test_wal_stream_500_entries_two_batches: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Test 3.4-B: streaming fewer than 256 entries produces exactly 1 partial batch.
|
||||
* Requirement 5.4: partial final batch sent immediately without waiting.
|
||||
*/
|
||||
static void test_wal_stream_partial_batch(void)
|
||||
{
|
||||
char tmpdir[256];
|
||||
snprintf(tmpdir, sizeof(tmpdir), "/tmp/xce_wal_stream_partial_%d_%ld",
|
||||
(int)getpid(), (long)time(NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, mkdir(tmpdir, 0700));
|
||||
|
||||
/* Write 10 entries starting at LSN 5 */
|
||||
int rc = write_test_wal_segment(tmpdir, /*start_lsn=*/5, /*n_entries=*/10);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
xce_wal_stream_request_hook = counting_wal_stream_hook;
|
||||
g_wal_stream_batch_count = 0;
|
||||
g_wal_stream_total_entries = 0;
|
||||
g_wal_stream_last_n_entries = 0;
|
||||
|
||||
/* Stream entries [6..14]: standby_lsn=5, committed_lsn=14 */
|
||||
rc = ha_stream_wal_to_standby(/*ep=*/NULL, tmpdir,
|
||||
/*standby_lsn=*/5,
|
||||
/*committed_lsn=*/14);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "ha_stream_wal_to_standby failed");
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(1, g_wal_stream_batch_count,
|
||||
"< 256 entries must produce exactly 1 partial batch");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(9, g_wal_stream_total_entries,
|
||||
"9 entries in range [6..14] must be sent");
|
||||
|
||||
xce_wal_stream_request_hook = NULL;
|
||||
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", tmpdir);
|
||||
system(cmd);
|
||||
printf("test_wal_stream_partial_batch: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Test 3.4-C: empty range (standby_lsn == committed_lsn) sends zero batches.
|
||||
* Requirement 5.4: no batch is sent when there are no entries to stream.
|
||||
*/
|
||||
static void test_wal_stream_empty_range_no_batches(void)
|
||||
{
|
||||
char tmpdir[256];
|
||||
snprintf(tmpdir, sizeof(tmpdir), "/tmp/xce_wal_stream_empty_%d_%ld",
|
||||
(int)getpid(), (long)time(NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, mkdir(tmpdir, 0700));
|
||||
|
||||
xce_wal_stream_request_hook = counting_wal_stream_hook;
|
||||
g_wal_stream_batch_count = 0;
|
||||
g_wal_stream_total_entries = 0;
|
||||
|
||||
/* standby_lsn == committed_lsn: nothing to stream */
|
||||
int rc = ha_stream_wal_to_standby(/*ep=*/NULL, tmpdir,
|
||||
/*standby_lsn=*/100,
|
||||
/*committed_lsn=*/100);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Empty range must return 0");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, g_wal_stream_batch_count,
|
||||
"Empty range must produce zero batches");
|
||||
|
||||
xce_wal_stream_request_hook = NULL;
|
||||
|
||||
char cmd[512];
|
||||
snprintf(cmd, sizeof(cmd), "rm -rf %s", tmpdir);
|
||||
system(cmd);
|
||||
printf("test_wal_stream_empty_range_no_batches: PASS\n");
|
||||
}
|
||||
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/* ── dispatch ───────────────────────────────────── */
|
||||
|
||||
void test_config_repl(void)
|
||||
|
|
@ -1367,6 +1544,10 @@ void test_config_repl(void)
|
|||
/* Task 2.3: OPCode_nop heartbeat reply carries stateReport */
|
||||
RUN_TEST(test_nop_reply_carries_state_report);
|
||||
RUN_TEST(test_nop_reply_zero_state_fresh_standby);
|
||||
/* Task 3.4: ha_stream_wal_to_standby batch WAL streaming */
|
||||
RUN_TEST(test_wal_stream_500_entries_two_batches);
|
||||
RUN_TEST(test_wal_stream_partial_batch);
|
||||
RUN_TEST(test_wal_stream_empty_range_no_batches);
|
||||
#endif
|
||||
/* Task A: raw capnp shims */
|
||||
RUN_TEST(test_replconfig_req_ssl_encode);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue