fix(controller): clamp committed_config_tag to own loaded config on promotion

bug-1462: raft_cb_become_leader baselined committed_config_tag to the
majority-safe floor across voters, correct for WAL LSN safety but wrong
for config tags — a leader's own actively-loaded config isn't
speculative like an unacknowledged WAL entry. On a fresh deploy every
peer reports tag=0 except the new primary, so the floor stuck at 0
forever and late-joining standbys never received a config push.

ha_promotion_config_tag_floor() clamps to max(safe_tag, self_tag),
mirroring bug-1060's WAL-truncation clamp pattern. Validated with
20-round sequential (63 jobs, zero loss) and 20-round chaotic
overlapping-kill (zero committed divergence, zero committed job loss)
HA failover soaks; unit suite 977/977 green.
This commit is contained in:
Rongsong Shen 2026-07-23 04:23:50 +00:00
parent ee8473bd99
commit 0649e17f62
3 changed files with 106 additions and 2 deletions

View file

@ -817,6 +817,28 @@ void ha_compute_safe_committed_lsn(ha_elect_ctx_t *ctx,
* producing divergent WALs and a permanent prevote deadlock. */
uint64_t ha_promotion_truncate_floor(uint64_t safe_lsn, uint64_t durable_tip);
/* ── bug-1462: config_tag floor for post-election committed_config_tag ───────
*
* ha_promotion_config_tag_floor: clamp the post-election committed_config_tag
* baseline so it never drops below the config the new leader already has
* loaded.
*
* Returns max(safe_tag, self_tag). safe_tag is the majority-threshold config
* tag from the vote replies (ha_compute_safe_committed_lsn); self_tag is the
* candidate's own actively-loaded config tag at election time. Unlike WAL LSN
* (where an unacknowledged entry is genuinely speculative), a node's own
* loaded config is never speculative — so the majority floor, correct for
* LSN, is wrong for config_tag.
*
* Root cause it fixes: on a cluster where no live admin config push had ever
* happened, every peer voter reported config_tag=0 while the new leader's own
* self_tag was a real, non-zero, loaded tag. The majority floor picked 0,
* permanently stalling the heartbeat-driven HA_REPL_PUSH_CONFIG decision for
* any standby not connected at the exact instant of promotion (a late
* joiner) — WAL/job replication was unaffected since it correctly floors at
* genuinely-speculative LSN 0. */
uint64_t ha_promotion_config_tag_floor(uint64_t safe_tag, uint64_t self_tag);
/* ── raft-drive-async task 1.1: peer-scaled liveness reservation ─────────────
*
* drive_async_liveness_reserved: PURE derivation of VTASK_LIVENESS_RESERVED —