feat(ha-term-safety): drive ACK path through raft_on_append_entries_reply + advance_commit (task 3.3)

Includes fix for bug-960: endpoint-type-aware peer-index resolution
(promoted-vote relative vs heartbeat-standby absolute) with fail-loud
bounds check, preventing an out-of-bounds match_index[] write / wrong-peer
false-quorum on the replication ACK path.
This commit is contained in:
Rongsong Shen 2026-06-22 09:25:44 +00:00
parent 754e586f8f
commit f1783f7952
4 changed files with 466 additions and 2 deletions

View file

@ -333,6 +333,47 @@ int ha_self_restart(cluster_t *c);
int ha_primary_log_append(cluster_t *c, uint32_t type,
const void *payload, uint32_t len, uint64_t *out_lsn);
/*
* ha_raft_on_repl_ack (ha-term-safety task 3.3, R4.1/R4.5): drive the library on
* a replication ACK. Routes a successful WalAck through raft_on_append_entries_reply
* (updating the library's match_index[]/next_index[] at the absolute peer index)
* then raft_advance_commit, so the library recomputes the current-term-gated
* commit (the Figure-8 rule) and MAY fire on_commit_advanced — the task-2.4
* callback that feeds the existing synchronous commit barrier. A current-term
* quorum advances committed only when the agreed entry is current-term; a
* prior-term agreed entry is held; a stale-term reply is dropped by the library.
*
* `abs_peer_idx` is the ABSOLUTE library peer index (j != self_idx), the same
* index space as match_index[]/next_index[]. The CALLER resolves it per endpoint
* type via ha_repl_ack_abs_peer_idx — promoted-vote eps carry a relative votes
* index, heartbeat standby eps carry an already-absolute index. This seam must
* NOT remap again. An out-of-range / self index is rejected loudly (returns
* -EINVAL, no library accounting) so the library's unchecked match_index[] write
* is never corrupted (no false quorum, no OOB). `acked_lsn` is the standby's
* matched index (WalAckReply.lsn); `reply_term` is the reply's term.
*
* Leader-only effect: raft_advance_commit returns -EPERM off the leader (no
* commit change, no callback) — harmless on a node that is no longer primary.
* Non-blocking (ha-async-io): the library performs no I/O; on_commit_advanced
* only records the barrier ceiling. Returns 0 on success; -EINVAL on a
* no-cluster context or a rejected peer index. */
int ha_raft_on_repl_ack(cluster_t *c, int abs_peer_idx, uint64_t acked_lsn,
uint64_t reply_term);
/*
* ha_repl_ack_abs_peer_idx (ha-term-safety task 3.3 remediation): resolve the
* ABSOLUTE library peer index from an ACK endpoint's stored peer_idx, branching
* on endpoint type (mirrors the standby_endpoints scan at ha.c:6187-6189):
* - ep_is_vote != 0 (promoted vote ep, vote_ep_ctx_t): peer_idx is a
* self-EXCLUDED votes index → remap abs = peer_idx < self_idx ? peer_idx
* : peer_idx+1.
* - ep_is_vote == 0 (heartbeat standby ep, standby_ep_ctx_t, ctx==NULL):
* peer_idx is ALREADY absolute → pass through.
* self_idx == ha->my_priority. Pure (no side effects); the single source of
* truth shared by the production ACK call site and the unit test so the
* remap-vs-passthrough distinction cannot drift. */
int ha_repl_ack_abs_peer_idx(ha_state_t *ha, int ep_is_vote, int peer_idx);
/*
* ha_schedule_demotion_convergence: schedule the one-shot delaymgr task that
* truncates above committed_lsn (Req 1.2), WARN-logs the discarded range