feat(ha-term-safety): retire term-0 repl_broadcast live send; carry leader term on replication (task 3.4)
Closes bug-990: the live replicator repl_broadcast wire-encoded term=0 (entry.term was never set), so standbys persisted hdr.term=0 and took the legacy direct-apply path instead of the library (term,lsn) intake. Now the live broadcast and catch-up streamer carry the leader's real term + AE prev tuple; ha_apply_wal_catchup_entry routes every term-carrying entry through raft_on_append_entries_request -> on_persist_entry (leader term stamped), retiring the term-0 direct-apply. Standbys now persist the leader's real term and reconcile by (term,lsn); the live commit chain is end-to-end. 848/849/850 + persist-before-ACK + commit-barrier + bug-960 preserved. Proven at integration level; the definitive live STRICT_TERM failover run is env-blocked here (bug-924 overlayfs) and recommended on a real fs.
This commit is contained in:
parent
87242aa8e9
commit
efe621c124
11 changed files with 307 additions and 45 deletions
|
|
@ -186,6 +186,11 @@ static uint8_t g_hook_last_entry_type;
|
|||
static const void *g_hook_last_payload;
|
||||
static size_t g_hook_last_payload_len;
|
||||
static int g_hook_return_val; /* 0 = success, negative = error */
|
||||
/* ha-term-safety task 3.4: capture the AppendEntries term + prev tuple the live
|
||||
* broadcast carries, so a test can assert the leader's term is on the wire. */
|
||||
static uint64_t g_hook_last_term;
|
||||
static uint64_t g_hook_last_prev_log_index;
|
||||
static uint64_t g_hook_last_prev_log_term;
|
||||
|
||||
static int test_broadcast_hook(void *ep, wal_catchup_entry_t **entries,
|
||||
int n_entries)
|
||||
|
|
@ -193,10 +198,13 @@ static int test_broadcast_hook(void *ep, wal_catchup_entry_t **entries,
|
|||
g_hook_call_count++;
|
||||
g_hook_last_ep = ep;
|
||||
if (n_entries >= 1 && entries && entries[0]) {
|
||||
g_hook_last_lsn = entries[0]->lsn;
|
||||
g_hook_last_entry_type = entries[0]->entry_type;
|
||||
g_hook_last_payload = entries[0]->raw_payload;
|
||||
g_hook_last_payload_len = entries[0]->raw_payload_len;
|
||||
g_hook_last_lsn = entries[0]->lsn;
|
||||
g_hook_last_entry_type = entries[0]->entry_type;
|
||||
g_hook_last_payload = entries[0]->raw_payload;
|
||||
g_hook_last_payload_len = entries[0]->raw_payload_len;
|
||||
g_hook_last_term = entries[0]->term;
|
||||
g_hook_last_prev_log_index = entries[0]->prev_log_index;
|
||||
g_hook_last_prev_log_term = entries[0]->prev_log_term;
|
||||
}
|
||||
return g_hook_return_val;
|
||||
}
|
||||
|
|
@ -210,6 +218,9 @@ static void reset_hook_state(void)
|
|||
g_hook_last_payload = NULL;
|
||||
g_hook_last_payload_len = 0;
|
||||
g_hook_return_val = 0;
|
||||
g_hook_last_term = 0;
|
||||
g_hook_last_prev_log_index = 0;
|
||||
g_hook_last_prev_log_term = 0;
|
||||
}
|
||||
|
||||
/* Test: no standbys — broadcast returns 0, hook not called */
|
||||
|
|
@ -223,7 +234,7 @@ static void test_broadcast_no_standbys_returns_zero(void)
|
|||
|
||||
wal_entry_hdr_t hdr = { .lsn = 10, .type = WAL_JOB_SUBMIT };
|
||||
uint8_t payload[] = { 0xAA, 0xBB };
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload));
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload), 0 /* prev_log_term */);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_hook_call_count);
|
||||
|
|
@ -249,7 +260,7 @@ static void test_broadcast_one_standby_sends_entry(void)
|
|||
|
||||
wal_entry_hdr_t hdr = { .lsn = 42, .type = WAL_JOB_SUBMIT };
|
||||
uint8_t payload[] = { 1, 2, 3, 4 };
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload));
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload), 0 /* prev_log_term */);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, g_hook_call_count);
|
||||
|
|
@ -267,6 +278,46 @@ static void test_broadcast_one_standby_sends_entry(void)
|
|||
#endif
|
||||
}
|
||||
|
||||
/* ha-term-safety task 3.4 (Req 1.3/1.4): the LIVE broadcast must carry the
|
||||
* leader's per-entry term and the AppendEntries prev tuple, so the standby
|
||||
* intake routes it through raft_on_append_entries_request and persists the
|
||||
* LEADER's term — not the term-0 legacy direct-apply path. Before the cutover
|
||||
* repl_broadcast sent term=0 (RED): the wire entry's term was always 0. */
|
||||
static void test_broadcast_carries_leader_term_and_prev_tuple(void)
|
||||
{
|
||||
#ifdef XCE_TESTING
|
||||
replicator_t r;
|
||||
int dummy_ep = 0;
|
||||
repl_init(&r);
|
||||
repl_add_standby(&r, (void *)&dummy_ep, "sb1", 7001);
|
||||
reset_hook_state();
|
||||
repl_broadcast_send_hook = test_broadcast_hook;
|
||||
|
||||
/* Leader appends LSN 5 at term 7; predecessor (LSN 4) was term 7. */
|
||||
wal_entry_hdr_t hdr = { .lsn = 5, .type = WAL_JOB_SUBMIT, .term = 7 };
|
||||
uint8_t payload[] = { 9, 8, 7 };
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload),
|
||||
7 /* prev_log_term of LSN 4 */);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, g_hook_call_count);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, g_hook_last_lsn);
|
||||
/* The load-bearing assertion: the leader's real term is on the wire entry
|
||||
* (NOT 0). This FAILS before the task 3.4 cutover. */
|
||||
TEST_ASSERT_EQUAL_UINT64(7, g_hook_last_term);
|
||||
/* prev tuple: prev_log_index == lsn - 1; prev_log_term == supplied. */
|
||||
TEST_ASSERT_EQUAL_UINT64(4, g_hook_last_prev_log_index);
|
||||
TEST_ASSERT_EQUAL_UINT64(7, g_hook_last_prev_log_term);
|
||||
|
||||
repl_broadcast_send_hook = NULL;
|
||||
repl_fini(&r);
|
||||
printf("test_broadcast_carries_leader_term_and_prev_tuple: PASS\n");
|
||||
#else
|
||||
printf("test_broadcast_carries_leader_term_and_prev_tuple: "
|
||||
"SKIPPED (no XCE_TESTING)\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Test: standby in catch-up is skipped — hook not called for it */
|
||||
static void test_broadcast_skips_catchup_standby(void)
|
||||
{
|
||||
|
|
@ -283,7 +334,7 @@ static void test_broadcast_skips_catchup_standby(void)
|
|||
|
||||
wal_entry_hdr_t hdr = { .lsn = 99, .type = WAL_JOB_SUBMIT };
|
||||
uint8_t payload[] = { 0xFF };
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload));
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload), 0 /* prev_log_term */);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
/* Only sb2 should be sent to */
|
||||
|
|
@ -313,7 +364,7 @@ static void test_broadcast_evicts_overloaded_standby(void)
|
|||
|
||||
wal_entry_hdr_t hdr = { .lsn = 5, .type = WAL_JOB_SUBMIT };
|
||||
uint8_t payload[] = { 0x01 };
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload));
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload), 0 /* prev_log_term */);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
/* Evicted — hook must NOT be called */
|
||||
|
|
@ -477,7 +528,7 @@ static void test_broadcast_send_failure_continues(void)
|
|||
|
||||
wal_entry_hdr_t hdr = { .lsn = 77, .type = WAL_JOB_SUBMIT };
|
||||
uint8_t payload[] = { 0xDE, 0xAD };
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload));
|
||||
int rc = repl_broadcast(&r, &hdr, payload, sizeof(payload), 0 /* prev_log_term */);
|
||||
|
||||
/* broadcast should still return 0 — errors are WARN+continue */
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
|
@ -548,6 +599,7 @@ void test_repl_unit(void) {
|
|||
/* Task 1.1: broadcast send path */
|
||||
RUN_TEST(test_broadcast_no_standbys_returns_zero);
|
||||
RUN_TEST(test_broadcast_one_standby_sends_entry);
|
||||
RUN_TEST(test_broadcast_carries_leader_term_and_prev_tuple);
|
||||
RUN_TEST(test_broadcast_skips_catchup_standby);
|
||||
RUN_TEST(test_broadcast_evicts_overloaded_standby);
|
||||
RUN_TEST(test_broadcast_send_failure_continues);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue