forked from lei.chen/myxce
feat(ha-repl-hardening): set committed_lsn from vote-derived safe point (task 4.2)
- In raft_cb_become_leader, after persist_truncate_wal_above(safe_lsn), store safe_lsn to ha->last_known_committed_lsn via atomic release - Test: 3-node voters [100,150,200] → safe_lsn=150; asserts ha->last_known_committed_lsn == 150 after raft_cb_become_leader_for_test
This commit is contained in:
parent
e7919304c3
commit
5915150c3b
3 changed files with 118 additions and 1 deletions
|
|
@ -155,7 +155,7 @@
|
|||
- _Requirements: 2.3_
|
||||
- _Boundary: Controller FSM (ha.c)_
|
||||
|
||||
- [ ] 4.2 Replace stale-ctrlhello truncation with vote-derived safe commit point
|
||||
- [x] 4.2 Replace stale-ctrlhello truncation with vote-derived safe commit point
|
||||
- Remove `persist_truncate_wal_above(last_known_committed_lsn)` from the become-leader path
|
||||
- Replace with `persist_truncate_wal_above(safe_committed_lsn)` using the value from task 4.1
|
||||
- Set `committed_lsn = safe_committed_lsn` after truncation
|
||||
|
|
|
|||
|
|
@ -468,6 +468,17 @@ static void raft_cb_become_leader(void *ctx, uint64_t term)
|
|||
if (c) {
|
||||
persist_truncate_wal_above(&c->persist, safe_lsn);
|
||||
}
|
||||
/* Task 4.2 (ha-repl-hardening): set committed_lsn = safe_lsn.
|
||||
* persist_truncate_wal_above already sets c->persist.committed_lsn
|
||||
* in the production path (c != NULL). Also update
|
||||
* ha->last_known_committed_lsn so the test-mode decision loop
|
||||
* (XCE_TESTING path in standby heartbeat reply handler) and any
|
||||
* code reading ha->last_known_committed_lsn see the safe commit
|
||||
* point immediately after promotion.
|
||||
* Design §Flow 0 step 5: committed_lsn = safe_committed_lsn.
|
||||
* Req 2.3 (ha-repl-hardening). */
|
||||
atomic_store_explicit(&ha->last_known_committed_lsn, safe_lsn,
|
||||
memory_order_release);
|
||||
/* Update committed config tag baseline for the COMMITTING decision loop */
|
||||
atomic_store_explicit(&ha->committed_config_tag, safe_tag,
|
||||
memory_order_release);
|
||||
|
|
|
|||
|
|
@ -608,6 +608,110 @@ static void test_safe_committed_lsn_one_peer_denied(void)
|
|||
#endif
|
||||
}
|
||||
|
||||
/* ── Task 4.2 (ha-repl-hardening): committed_lsn set to safe_committed_lsn ──
|
||||
*
|
||||
* raft_cb_become_leader must set ha->last_known_committed_lsn to the value
|
||||
* returned by ha_compute_safe_committed_lsn after WAL truncation.
|
||||
*
|
||||
* This ensures that in test mode (c == NULL) the decision loop reads the
|
||||
* correct committed_lsn from ha->last_known_committed_lsn (see decision-loop
|
||||
* XCE_TESTING path in ha.c).
|
||||
*
|
||||
* Requirement 2.3 (ha-repl-hardening): safe_committed_lsn is derived from
|
||||
* voter replies; entries at or below it survive truncation; entries above are
|
||||
* removed.
|
||||
*
|
||||
* The test uses raft_cb_become_leader_for_test (XCE_TESTING shim) which calls
|
||||
* raft_cb_become_leader with ha->raft_c = NULL (no real cluster, no WAL I/O).
|
||||
* The WAL-level truncation behaviour (file entries above safe_lsn deleted) is
|
||||
* covered by test_persist_truncate.c; this test focuses on the committed_lsn
|
||||
* field assignment that task 4.2 adds.
|
||||
*
|
||||
* Design ref: design.md §Flow 0 step 5: committed_lsn = safe_committed_lsn
|
||||
*/
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
extern void raft_cb_become_leader_for_test(ha_state_t *ha, uint64_t term);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Test E: raft_cb_become_leader sets ha->last_known_committed_lsn = safe_lsn.
|
||||
*
|
||||
* 3-node cluster (n_other=2, votes_granted=3):
|
||||
* votes[0]: my_lsn=200, peer_lsn=100, vote_granted=1
|
||||
* votes[1]: peer_lsn=150, vote_granted=1
|
||||
*
|
||||
* Sorted granted LSNs desc: [200 (self), 150 (peer1), 100 (peer0)]
|
||||
* Majority threshold for 3 granted = ceil(3/2) = 2; element at index 1 = 150
|
||||
* → safe_committed_lsn = 150
|
||||
*
|
||||
* After raft_cb_become_leader_for_test, ha->last_known_committed_lsn must == 150.
|
||||
*
|
||||
* Additionally verify via ha_compute_safe_committed_lsn that entry AT safe_lsn
|
||||
* (lsn=150) is the boundary: safe_lsn=150 means truncate ABOVE 150 (entries
|
||||
* 151+ removed); entry at 150 is preserved.
|
||||
*/
|
||||
static void test_become_leader_sets_committed_lsn_to_safe_lsn(void)
|
||||
{
|
||||
#ifdef XCE_TESTING
|
||||
reset_counters();
|
||||
|
||||
ha_state_t ha;
|
||||
ha_init(&ha, &g_cfg, 0);
|
||||
ha.phase2_done = 1; /* skip phase2 in this test */
|
||||
|
||||
/* Build elect_ctx with 2 peers; safe_lsn will be 150 */
|
||||
ha_elect_ctx_t *ctx = calloc(1, sizeof(*ctx) + 2 * sizeof(ctx->votes[0]));
|
||||
TEST_ASSERT_NOT_NULL(ctx);
|
||||
ctx->n_peers = 2;
|
||||
ctx->votes_granted = 3; /* self + 2 peers */
|
||||
ctx->n_replied = 2;
|
||||
pthread_mutex_init(&ctx->lock, NULL);
|
||||
sem_init(&ctx->done, 0, 0);
|
||||
|
||||
/* votes[0]: self data (my_lsn=200) + peer0 granted at lsn=100 */
|
||||
ctx->votes[0].my_lsn = 200;
|
||||
ctx->votes[0].my_tag = 5;
|
||||
ctx->votes[0].vote_granted = 1;
|
||||
ctx->votes[0].peer_lsn = 100;
|
||||
ctx->votes[0].peer_tag = 5;
|
||||
|
||||
/* votes[1]: peer1 granted at lsn=150 */
|
||||
ctx->votes[1].vote_granted = 1;
|
||||
ctx->votes[1].peer_lsn = 150;
|
||||
ctx->votes[1].peer_tag = 5;
|
||||
|
||||
/* Verify the safe_lsn computation independently */
|
||||
uint64_t safe_lsn = 0, safe_tag = 0;
|
||||
ha_compute_safe_committed_lsn(ctx, &safe_lsn, &safe_tag);
|
||||
TEST_ASSERT_EQUAL_UINT64_MESSAGE(150, safe_lsn,
|
||||
"ha_compute_safe_committed_lsn: voters [100,150]+self=200 → safe_lsn must be 150");
|
||||
|
||||
/* Wire elect_ctx into ha_state so raft_cb_become_leader can read it */
|
||||
atomic_store_explicit(&ha.elect_ctx, (void *)ctx, memory_order_release);
|
||||
|
||||
/* Drive the leader promotion path (c=NULL → no WAL I/O, no phase2) */
|
||||
raft_cb_become_leader_for_test(&ha, /*term=*/1);
|
||||
|
||||
/* Task 4.2: ha->last_known_committed_lsn must equal safe_lsn (150) */
|
||||
uint64_t committed_lsn =
|
||||
atomic_load_explicit(&ha.last_known_committed_lsn, memory_order_acquire);
|
||||
TEST_ASSERT_EQUAL_UINT64_MESSAGE(150, committed_lsn,
|
||||
"raft_cb_become_leader must set ha->last_known_committed_lsn = safe_lsn (150); "
|
||||
"WAL entry AT safe_lsn=150 is preserved; entries above 150 are truncated");
|
||||
|
||||
sem_destroy(&ctx->done);
|
||||
pthread_mutex_destroy(&ctx->lock);
|
||||
free(ctx);
|
||||
ha_fini(&ha);
|
||||
clear_hook();
|
||||
printf("test_become_leader_sets_committed_lsn_to_safe_lsn: PASS "
|
||||
"(committed_lsn=%" PRIu64 ")\n", committed_lsn);
|
||||
#else
|
||||
printf("test_become_leader_sets_committed_lsn_to_safe_lsn: SKIP (non-test build)\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ── Suite entry point ───────────────────────────────────────────────────── */
|
||||
|
||||
void test_ha_evaluate_committing(void)
|
||||
|
|
@ -627,4 +731,6 @@ void test_ha_evaluate_committing(void)
|
|||
RUN_TEST(test_safe_committed_lsn_two_peers_at_50_and_51);
|
||||
RUN_TEST(test_safe_committed_lsn_single_node);
|
||||
RUN_TEST(test_safe_committed_lsn_one_peer_denied);
|
||||
/* Task 4.2 (ha-repl-hardening): committed_lsn set to safe_committed_lsn */
|
||||
RUN_TEST(test_become_leader_sets_committed_lsn_to_safe_lsn);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue