feat(ha-repl-hardening): audit safe commit point and snapshot transfer tests (tasks 6.3, 6.4)
6.3: Fix single-node bug — ha_compute_safe_committed_lsn now returns self_lsn instead of 0 for n_peers=0 (prevents WAL truncation on single-node re-election). Add self_lsn/self_tag to ha_elect_ctx_t, populated unconditionally in ha_start_election_async. All 3 test scenarios pass under ASAN. 6.4: Add test_snapshot_recv_128kb_two_chunks (128KB snapshot in 2x64KB chunks, verifies size and applied_lsn); 4 other test cases already existed from tasks 3.5c/3.5d. All ASAN-clean.
This commit is contained in:
parent
d41cfc5813
commit
345d51dd61
5 changed files with 160 additions and 23 deletions
|
|
@ -348,14 +348,15 @@ static void raft_cb_start_election_after_delay(void *ctx, uint32_t delay_ms)
|
|||
*
|
||||
* Algorithm (design §Flow 0: Post-Election — Determine Safe Commit Point):
|
||||
* 1. Collect LSNs from all granted voters:
|
||||
* - Self always granted: self_lsn = votes[0].my_lsn (when n_peers > 0)
|
||||
* - Self always granted: self_lsn = ctx->self_lsn (stored unconditionally)
|
||||
* - Each peer with vote_granted=1: lsn = votes[i].peer_lsn
|
||||
* 2. Sort granted LSNs descending.
|
||||
* 3. safe_committed_lsn = sorted[floor(n_granted / 2)]
|
||||
* (the majority-threshold element — the highest LSN held by a majority)
|
||||
* 4. Same logic for config tags (votes[i].my_tag / votes[i].peer_tag).
|
||||
* 4. Same logic for config tags (ctx->self_tag / votes[i].peer_tag).
|
||||
*
|
||||
* Single-node (n_peers == 0): votes[] is empty, outputs 0 for both fields.
|
||||
* Single-node (n_peers == 0): votes[] is empty; safe_committed_lsn = ctx->self_lsn.
|
||||
* This prevents WAL truncation to 0 on re-election in a single-node cluster.
|
||||
*
|
||||
* Requirements: 2.3 (ha-repl-hardening spec)
|
||||
* Boundary: Controller FSM (ha.c)
|
||||
|
|
@ -375,10 +376,11 @@ void ha_compute_safe_committed_lsn(ha_elect_ctx_t *ctx,
|
|||
{
|
||||
if (!ctx || !safe_lsn_out || !safe_tag_out) return;
|
||||
|
||||
/* Single-node: no votes[] entries — self is the only majority. */
|
||||
/* Single-node: no votes[] entries — self is the only majority.
|
||||
* Return self_lsn so WAL is not truncated to 0 on re-election. */
|
||||
if (ctx->n_peers == 0) {
|
||||
*safe_lsn_out = 0;
|
||||
*safe_tag_out = 0;
|
||||
*safe_lsn_out = ctx->self_lsn;
|
||||
*safe_tag_out = ctx->self_tag;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -397,9 +399,9 @@ void ha_compute_safe_committed_lsn(ha_elect_ctx_t *ctx,
|
|||
|
||||
int n_granted = 0;
|
||||
|
||||
/* Self always granted: self's lsn/tag lives in votes[0].my_lsn / my_tag */
|
||||
lsns[n_granted] = ctx->votes[0].my_lsn;
|
||||
tags[n_granted] = ctx->votes[0].my_tag;
|
||||
/* Self always granted: self's lsn/tag from the dedicated self_lsn/self_tag fields */
|
||||
lsns[n_granted] = ctx->self_lsn;
|
||||
tags[n_granted] = ctx->self_tag;
|
||||
n_granted++;
|
||||
|
||||
/* Each peer with vote_granted=1 */
|
||||
|
|
@ -3073,7 +3075,13 @@ void ha_start_election_async(ha_state_t *ha, cluster_t *c,
|
|||
pthread_mutex_init(&ctx->lock, NULL);
|
||||
sem_init(&ctx->done, 0, 0);
|
||||
|
||||
/* Store candidate's own tag/lsn; votes[0] is read by raft_cb_send_vote_request */
|
||||
/* Store candidate's own tag/lsn unconditionally in self_lsn/self_tag (task 6.3):
|
||||
* ha_compute_safe_committed_lsn uses these for the majority computation,
|
||||
* including the single-node case where votes[] is empty. */
|
||||
ctx->self_lsn = my_lsn;
|
||||
ctx->self_tag = my_config_tag;
|
||||
|
||||
/* Also store in votes[0] for raft_cb_send_vote_request (multi-node only) */
|
||||
if (n_other > 0) {
|
||||
ctx->votes[0].my_tag = my_config_tag;
|
||||
ctx->votes[0].my_lsn = my_lsn;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue