forked from lei.chen/myxce
feat(ha-repl-hardening): wire OPCode_walCatchupEntry standby handler (task 2.1)
Replace stub with ha_apply_wal_catchup_entry() calling apply_wal_entry(). CAS-advance applied_lsn on success; log ERROR and skip on failure. Standby stays in CTRL_STANDBY throughout. 3 new unit tests.
This commit is contained in:
parent
e3ca775f0d
commit
647b88ce57
5 changed files with 251 additions and 1 deletions
|
|
@ -23,11 +23,23 @@
|
|||
|
||||
#include "config_repl.h"
|
||||
#include "controller.capnp.h"
|
||||
#include "ha.h"
|
||||
#include "myconf.h"
|
||||
#include "persist.h"
|
||||
#include "request.h"
|
||||
#include "typemap.h"
|
||||
#include "wal_types.h"
|
||||
#include "xce/cluster_def.h"
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/* Declared in cluster.c under #ifdef XCE_TESTING — test seam for
|
||||
* ha_apply_wal_catchup_entry: applies a single WAL catch-up entry to
|
||||
* persistent storage and advances ha->applied_lsn on success.
|
||||
* On error: logs ERROR, returns non-zero, does not crash. */
|
||||
int ha_apply_wal_catchup_entry(ha_state_t *ha, persist_t *p,
|
||||
wal_catchup_entry_t *entry);
|
||||
#endif
|
||||
|
||||
/* Per-test directory helpers — not Unity setUp/tearDown (those are in
|
||||
* controller_test.c). Each test that needs a temp dir calls cr_setup()
|
||||
* at the top and cr_teardown() via the label before returning. */
|
||||
|
|
@ -900,6 +912,141 @@ static void test_upgrade_config_req_extract_raw(void)
|
|||
printf("test_upgrade_config_req_extract_raw: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Task 2.1: OPCode_walCatchupEntry handler ────────────────────────────── */
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
|
||||
/* Helpers shared by Task 2.1 tests: create/destroy a minimal persist_t. */
|
||||
|
||||
static persist_t *make_test_persist_catchup(char *tmpdir_out, size_t sz)
|
||||
{
|
||||
persist_t *p = calloc(1, sizeof(persist_t));
|
||||
if (!p) { fprintf(stderr, "calloc failed\n"); exit(1); }
|
||||
|
||||
persist_config_t cfg = persist_config_defaults();
|
||||
cfg.wal_sync_ms = 5000; /* no real fsync in unit tests */
|
||||
|
||||
snprintf(tmpdir_out, sz, "/tmp/xce_catchup_XXXXXX");
|
||||
if (!mkdtemp(tmpdir_out)) {
|
||||
fprintf(stderr, "mkdtemp failed\n");
|
||||
free(p);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
persist_set_workdir(p, tmpdir_out, "ctrl-catchup");
|
||||
persist_init(p, &cfg, NULL);
|
||||
persist_start(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void destroy_test_persist_catchup(persist_t *p)
|
||||
{
|
||||
persist_stop(p);
|
||||
persist_fini(p);
|
||||
free(p);
|
||||
}
|
||||
|
||||
/* Test 2.1-A: successful single WAL entry advances applied_lsn */
|
||||
static void test_wal_catchup_entry_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 a single-entry WAL catch-up with LSN=42, no payload */
|
||||
wal_catchup_entry_t entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
entry.lsn = 42;
|
||||
entry.entry_type = (uint8_t)WAL_JOB_UPDATE;
|
||||
entry.payload = NULL; /* payload_len=0; apply_wal_entry handles NULL */
|
||||
entry.n_entries = 0;
|
||||
entry.entries = NULL;
|
||||
|
||||
/* GREEN will implement ha_apply_wal_catchup_entry() */
|
||||
int rc = ha_apply_wal_catchup_entry(&ha, p, &entry);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
/* applied_lsn must advance to lsn+1 = 43 (mirrors persist->current_lsn) */
|
||||
TEST_ASSERT_EQUAL_UINT64(43,
|
||||
(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_entry_advances_applied_lsn: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 2.1-B: apply_wal_entry failure (NULL persist = -EINVAL) is logged and
|
||||
* skipped — applied_lsn must NOT advance, handler must not crash.
|
||||
* Req 3.3: corrupt/error entries are logged and skipped without crashing. */
|
||||
static void test_wal_catchup_entry_error_skipped_no_crash(void)
|
||||
{
|
||||
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);
|
||||
atomic_store_explicit(&ha.applied_lsn, 10, memory_order_release);
|
||||
|
||||
/* Pass NULL persist — apply_wal_entry returns -EINVAL for NULL p */
|
||||
wal_catchup_entry_t entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
entry.lsn = 99;
|
||||
entry.entry_type = (uint8_t)WAL_JOB_UPDATE;
|
||||
entry.payload = NULL;
|
||||
entry.n_entries = 0;
|
||||
entry.entries = NULL;
|
||||
|
||||
/* Handler must NOT crash; must log ERROR; applied_lsn must remain at 10 */
|
||||
int rc = ha_apply_wal_catchup_entry(&ha, /*persist=*/NULL, &entry);
|
||||
/* rc is non-zero (error propagated from apply_wal_entry(-EINVAL)) */
|
||||
TEST_ASSERT_TRUE(rc != 0);
|
||||
/* applied_lsn must not advance on failure */
|
||||
TEST_ASSERT_EQUAL_UINT64(10,
|
||||
(uint64_t)atomic_load_explicit(&ha.applied_lsn, memory_order_acquire));
|
||||
|
||||
ha_fini(&ha);
|
||||
printf("test_wal_catchup_entry_error_skipped_no_crash: PASS\n");
|
||||
}
|
||||
|
||||
/* Test 2.1-C: NULL entry is handled gracefully (null guard) */
|
||||
static void test_wal_catchup_entry_null_entry_no_crash(void)
|
||||
{
|
||||
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);
|
||||
|
||||
/* NULL entry — must not crash, must return error */
|
||||
int rc = ha_apply_wal_catchup_entry(&ha, NULL, NULL);
|
||||
TEST_ASSERT_TRUE(rc != 0);
|
||||
|
||||
ha_fini(&ha);
|
||||
printf("test_wal_catchup_entry_null_entry_no_crash: PASS\n");
|
||||
}
|
||||
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/* ── dispatch ───────────────────────────────────── */
|
||||
|
||||
void test_config_repl(void)
|
||||
|
|
@ -926,6 +1073,10 @@ void test_config_repl(void)
|
|||
RUN_TEST(test_load_primary_config_missing_returns_error);
|
||||
RUN_TEST(test_wait_all_with_timeout_slow_threads);
|
||||
RUN_TEST(test_wait_all_with_timeout_fast_threads);
|
||||
/* Task 2.1: OPCode_walCatchupEntry handler */
|
||||
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);
|
||||
#endif
|
||||
/* Task A: raw capnp shims */
|
||||
RUN_TEST(test_replconfig_req_ssl_encode);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue