feat(ha-heartbeat-committed-propagation): stamp own committed+term on primary heartbeat (task 3.1)
primary_heartbeat_fn now sends xce_ha_heartbeat_req with the primary's OWN atomic ha->term / persist.committed_lsn / committed_config_tag (NOT last_known_*), snapshotted once per tick (Req 1.1, 3.4). XCE_TESTING seam adds a capturing ha_heartbeat_req_hook (precedence new->legacy->real) so existing ha_nop_req_hook liveness tests stay green; send-failure WARN + last_push_time unchanged. New test poisons last_known_* to prove own-committed. Direct binary 119/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
90e7179a89
commit
d534f83e46
4 changed files with 158 additions and 3 deletions
|
|
@ -2764,6 +2764,17 @@ int (*ha_push_config_hook)(void *c) = NULL;
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
int (*ha_heartbeat_req_hook)(endpoint_t *ep, uint64_t term,
|
||||
uint64_t committed_lsn,
|
||||
uint64_t committed_config_tag) = NULL;
|
||||
|
||||
/*
|
||||
* ha_repl_push_config_for_standby_hook: test-only seam for the per-standby
|
||||
* config push triggered by the heartbeat-reply decision loop (Task 3.3).
|
||||
|
|
@ -5286,6 +5297,20 @@ static int primary_heartbeat_fn(void *arg)
|
|||
if (timeout_s == 0)
|
||||
timeout_s = HA_DEFAULT_HEARTBEAT_TIMEOUT_MS / 1000;
|
||||
|
||||
/* Stamp the primary's OWN current committed position + term onto
|
||||
* every heartbeat this tick (Req 1.1, 3.4). Read atomically here,
|
||||
* once per tick, so every standby sees a consistent snapshot. Use
|
||||
* the primary's authoritative state — NOT the standby-side
|
||||
* last_known_* fields. */
|
||||
uint64_t hb_term =
|
||||
atomic_load_explicit(&ha->term, memory_order_acquire);
|
||||
uint64_t hb_committed_lsn =
|
||||
atomic_load_explicit(&c->persist.committed_lsn,
|
||||
memory_order_acquire);
|
||||
uint64_t hb_committed_config_tag =
|
||||
atomic_load_explicit(&ha->committed_config_tag,
|
||||
memory_order_acquire);
|
||||
|
||||
/* Endpoints evicted this tick; their repl deregistration runs AFTER
|
||||
* ha->lock is released (repl lock-ordering — mirrors the err handlers). */
|
||||
endpoint_t *evicted[MAX_HA_CANDIDATES];
|
||||
|
|
@ -5324,10 +5349,24 @@ static int primary_heartbeat_fn(void *arg)
|
|||
|
||||
time_t elapsed = now - sctx->last_push_time;
|
||||
if (elapsed >= (time_t)interval_s) {
|
||||
/* Heartbeat carries the primary's own committed + term
|
||||
* (Req 1.1, 3.4). */
|
||||
#ifdef XCE_TESTING
|
||||
int rc = ha_nop_req_hook ? ha_nop_req_hook(ep) : xce_nop_req(ep);
|
||||
int rc;
|
||||
if (ha_heartbeat_req_hook)
|
||||
rc = ha_heartbeat_req_hook(ep, hb_term,
|
||||
hb_committed_lsn,
|
||||
hb_committed_config_tag);
|
||||
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);
|
||||
#else
|
||||
int rc = xce_nop_req(ep);
|
||||
int rc = xce_ha_heartbeat_req(ep, hb_term,
|
||||
hb_committed_lsn,
|
||||
hb_committed_config_tag);
|
||||
#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