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

@ -814,6 +814,13 @@ uint64_t ha_promotion_truncate_floor(uint64_t safe_lsn, uint64_t durable_tip)
return safe_lsn > durable_tip ? safe_lsn : durable_tip;
}
/* ── bug-1462: never floor committed_config_tag below the leader's own
* loaded config — see ha.h for full rationale. */
uint64_t ha_promotion_config_tag_floor(uint64_t safe_tag, uint64_t self_tag)
{
return safe_tag > self_tag ? safe_tag : self_tag;
}
/* ── raft-drive-async task 1.1: peer-scaled liveness reservation ─────────────
*
* Pure: reserved = max(1, min(n_peers - 1, n_vcpus - 1)). The floor of 1 is
@ -1275,8 +1282,16 @@ static void raft_cb_become_leader(void *ctx, uint64_t term)
* 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 post-election decision loop */
atomic_store_explicit(&ha->committed_config_tag, safe_tag,
/* Update committed config tag baseline for the post-election decision
* loop. bug-1462: clamp to the leader's OWN loaded config tag —
* unlike WAL LSN, a node's actively-loaded config is never
* speculative, so the bare majority floor (correct for LSN) must
* not be allowed to strand a real config behind an all-zero floor
* reported by voters that never received a live config push.
* See ha_promotion_config_tag_floor's doc comment (ha.h). */
uint64_t committed_tag_floor =
ha_promotion_config_tag_floor(safe_tag, ectx->self_tag);
atomic_store_explicit(&ha->committed_config_tag, committed_tag_floor,
memory_order_release);
}
}