fix(controller): record standby state under its absolute peer index (bug-1891)
A promoted vote endpoint carries a peer index relative to the votes array (which leaves out the primary); a late-joiner's heartbeat endpoint carries the absolute index. repl_ack_decode_and_dispatch recorded state reports under the raw index, so with ctrl-1 primary, ctrl-3's vote endpoint (relative 1) and a rejoined ctrl-2's heartbeat endpoint (absolute 1) shared standby_reported_*[1]. Each handler then acted on the other's position over its own endpoint: the snapshot meant for the empty, rejoining ctrl-2 went to the caught-up ctrl-3, and ctrl-2 never caught up. Resolve the absolute index with ha_repl_ack_abs_peer_idx (as the WAL-ack branch already did) for the state report and for ha_walack_on_ack, and size catchup_inflight by MAX_HA_CANDIDATES so every absolute index fits. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1922ac803e
commit
4922f6c844
6 changed files with 188 additions and 10 deletions
|
|
@ -6870,8 +6870,14 @@ static int repl_ack_decode_and_dispatch(endpoint_t *ep, vote_ep_ctx_t *vctx,
|
|||
(void)raft_on_higher_term_observed(&ha->raft, sr.term);
|
||||
pthread_mutex_unlock(&ha->raft_lock);
|
||||
|
||||
/* peer_idx from standby_ep_ctx_t (layout-compatible with vote_ep_ctx_t) */
|
||||
int peer_idx = vctx->peer_idx;
|
||||
/* bug-1891: the ABSOLUTE peer index. A promoted vote ep carries a
|
||||
* relative votes index (see ha_repl_ack_abs_peer_idx); recording
|
||||
* its report under the raw index shared a slot with another
|
||||
* standby's heartbeat ep, so one handler acted on the other's
|
||||
* position -- e.g. sent a rejoining standby's snapshot to a peer
|
||||
* that was already caught up. */
|
||||
int peer_idx = ha_repl_ack_abs_peer_idx(
|
||||
ha, /*ep_is_vote*/ vctx->ctx != NULL, vctx->peer_idx);
|
||||
uint64_t committed_lsn = 0;
|
||||
uint64_t committed_tag = 0;
|
||||
uint64_t last_written = 0; /* latest WAL LSN — may be > committed */
|
||||
|
|
@ -6961,7 +6967,7 @@ static int repl_ack_decode_and_dispatch(endpoint_t *ep, vote_ep_ctx_t *vctx,
|
|||
* decision the unit test drives via ha_walack_continuation_for_test.
|
||||
* Scheduling is O(1) on iomgr; all WAL file I/O runs in the
|
||||
* deferred continuation task. */
|
||||
ha_walack_on_ack(c, ep, vctx->peer_idx, acked_lsn);
|
||||
ha_walack_on_ack(c, ep, ack_abs_idx, acked_lsn); /* bug-1891 */
|
||||
} else if (!success) {
|
||||
/* bug-1421: a WAL NACK can also carry a higher term (the
|
||||
* standby rejected this entry as stale) — route it through
|
||||
|
|
@ -7043,6 +7049,26 @@ static int repl_ack_decode_and_dispatch(endpoint_t *ep, vote_ep_ctx_t *vctx,
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/* bug-1891: drive repl_ack_decode_and_dispatch for one inbound packet on a
|
||||
* promoted vote endpoint (ep_is_vote=1, peer_idx relative to the votes
|
||||
* array) or a heartbeat standby endpoint (ep_is_vote=0, peer_idx absolute).
|
||||
* Takes ownership of pkt. */
|
||||
int ha_repl_ack_dispatch_for_test(ha_state_t *ha, endpoint_t *ep,
|
||||
int ep_is_vote, int peer_idx,
|
||||
io_packet_t *pkt)
|
||||
{
|
||||
static ha_elect_ctx_t vote_ctx; /* only its non-NULL-ness is read */
|
||||
vote_ep_ctx_t vctx;
|
||||
|
||||
memset(&vctx, 0, sizeof(vctx));
|
||||
vctx.ha = ha;
|
||||
vctx.ctx = ep_is_vote ? &vote_ctx : NULL;
|
||||
vctx.peer_idx = peer_idx;
|
||||
return repl_ack_decode_and_dispatch(ep, &vctx, pkt);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* peer_ctrl_msg_handler_inline: the state-aware peer-control routing body.
|
||||
* Routes by ha->role at dispatch time — no explicit handler reassignment needed.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue