feat(controller-status): carry election time on the heartbeat (task 7.3)
The primary echoes its own last_known_elected_at_ms on every outgoing heartbeat (threaded through xce_ha_heartbeat_req's existing scalar-arg plumbing, alongside term/committed_lsn/committed_config_tag); a standby processing a received heartbeat (ha_consume_heartbeat_committed, the sole production call site) stores it unconditionally into its own last_known_elected_at_ms, reconcile-free -- no other HA field moves. ha_observe_heartbeat_committed itself is untouched; the new store sits next to its call site, not inside it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
21ae00b375
commit
30adc52ea3
9 changed files with 252 additions and 33 deletions
|
|
@ -5901,12 +5901,14 @@ int (*ha_nop_req_hook)(endpoint_t *ep) = NULL;
|
|||
* ha_heartbeat_req_hook: test-only seam for the HA heartbeat send in
|
||||
* primary_heartbeat_fn. When non-NULL, primary_heartbeat_fn calls this INSTEAD
|
||||
* OF xce_ha_heartbeat_req with the SAME (term, committed_lsn,
|
||||
* committed_config_tag) it stamps onto the wire, so tests can assert the
|
||||
* heartbeat carries the primary's own current committed state. Req 1.1, 3.4.
|
||||
* committed_config_tag, elected_at_ms) it stamps onto the wire, so tests can
|
||||
* assert the heartbeat carries the primary's own current committed state and
|
||||
* election time. Req 1.1, 3.4, 12.2.
|
||||
*/
|
||||
int (*ha_heartbeat_req_hook)(endpoint_t *ep, uint64_t term,
|
||||
uint64_t committed_lsn,
|
||||
uint64_t committed_config_tag) = NULL;
|
||||
uint64_t committed_config_tag,
|
||||
uint64_t elected_at_ms) = NULL;
|
||||
|
||||
/*
|
||||
* ha_reconnect_connect_hook: test-only seam for the BLOCKING half of the
|
||||
|
|
@ -9257,6 +9259,19 @@ void ha_consume_heartbeat_committed(cluster_t *c, const heartbeat_msg_t *hb)
|
|||
ha_observe_heartbeat_committed(c, hb->committed_lsn,
|
||||
hb->committed_config_tag);
|
||||
|
||||
/* (Req 12.2) Learn the primary's election time. Unconditional overwrite —
|
||||
* no monotonic/term compare needed, unlike the committed-position observe
|
||||
* above: this call site already passed the non-primary gate + stale-term
|
||||
* guard above, i.e. it is already gated on "this heartbeat is from my
|
||||
* currently-recognized leader," the SAME precondition
|
||||
* ha_observe_heartbeat_committed's own committed_lsn/committed_config_tag
|
||||
* learn relies on. Deliberately NOT added inside
|
||||
* ha_observe_heartbeat_committed itself (that function's contract/tests
|
||||
* are unchanged) — a separate, unconditional store immediately alongside
|
||||
* it, reconcile-free (touches nothing else). */
|
||||
atomic_store_explicit(&c->ha.last_known_elected_at_ms, hb->elected_at_ms,
|
||||
memory_order_release);
|
||||
|
||||
/* 2026-07-10: drive this node's own committed_lsn + apply pump from the
|
||||
* position just learned (see ha_standby_advance_committed_and_apply) —
|
||||
* the commit-then-apply half of the standby ingest path. */
|
||||
|
|
@ -9874,6 +9889,14 @@ static int primary_heartbeat_fn(void *arg)
|
|||
uint64_t hb_committed_config_tag =
|
||||
atomic_load_explicit(&ha->committed_config_tag,
|
||||
memory_order_acquire);
|
||||
/* Req 12.2: echo the primary's own currently-known election time
|
||||
* on every heartbeat so standbys can learn it. Self-stamped by
|
||||
* both promotion roads (task 7.2) at the instant this node
|
||||
* committed to CTRL_PRIMARY; read unconditionally here, same
|
||||
* shape as hb_term/hb_committed_lsn/hb_committed_config_tag. */
|
||||
uint64_t hb_elected_at_ms =
|
||||
atomic_load_explicit(&ha->last_known_elected_at_ms,
|
||||
memory_order_acquire);
|
||||
|
||||
/* Endpoints evicted this tick; their repl deregistration runs AFTER
|
||||
* ha->lock is released (repl lock-ordering — mirrors the err handlers). */
|
||||
|
|
@ -9920,17 +9943,20 @@ static int primary_heartbeat_fn(void *arg)
|
|||
if (ha_heartbeat_req_hook)
|
||||
rc = ha_heartbeat_req_hook(ep, hb_term,
|
||||
hb_committed_lsn,
|
||||
hb_committed_config_tag);
|
||||
hb_committed_config_tag,
|
||||
hb_elected_at_ms);
|
||||
else if (ha_nop_req_hook)
|
||||
rc = ha_nop_req_hook(ep);
|
||||
else
|
||||
rc = xce_ha_heartbeat_req(ep, hb_term,
|
||||
hb_committed_lsn,
|
||||
hb_committed_config_tag);
|
||||
hb_committed_config_tag,
|
||||
hb_elected_at_ms);
|
||||
#else
|
||||
int rc = xce_ha_heartbeat_req(ep, hb_term,
|
||||
hb_committed_lsn,
|
||||
hb_committed_config_tag);
|
||||
hb_committed_config_tag,
|
||||
hb_elected_at_ms);
|
||||
#endif
|
||||
if (rc < 0)
|
||||
WARN("HA: heartbeat nop to standby[%d] failed: %d", i, rc);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue