feat(ha-repl-hardening): wire config push from decision loop (task 3.3)

ha_dispatch_repl_action_for_standby() calls push_config_to_standbys()
when PUSH_CONFIG decision. Hook seam for unit tests. Wired into
repl_ack_decode_and_dispatch after ha_process_standby_state.
oldest_wal_lsn=0 placeholder documented for task 3.4.
This commit is contained in:
Rongsong Shen 2026-06-04 17:23:46 +00:00
parent a475ef621f
commit d1c7872d73
4 changed files with 258 additions and 1 deletions

View file

@ -1009,6 +1009,92 @@ void ha_process_standby_state(ha_state_t *ha, int peer_idx,
}
}
/*
* ha_dispatch_repl_action_for_standby — execute the decision-loop action for
* one standby on the current heartbeat cycle (Task 3.3).
*
* Reads ha->standby_reported_tag[peer_idx] / standby_reported_lsn[peer_idx]
* (stored by ha_process_standby_state on the same heartbeat reply), calls
* ha_evaluate_repl_action, and acts on the result:
*
* HA_REPL_PUSH_CONFIG → push_config_to_standbys(c, …) [or test hook]
* HA_REPL_SEND_SNAPSHOT→ stub (Task 3.4)
* HA_REPL_STREAM_WAL → stub (Task 3.5)
* HA_REPL_CONVERGED → no-op
*
* One action per heartbeat cycle per standby (design §Unified Heartbeat-Driven
* Protocol). Returns 0 on success, -EINVAL on bad peer_idx.
*
* Requirements: 1.4 (ha-repl-hardening spec)
*/
int ha_dispatch_repl_action_for_standby(ha_state_t *ha,
cluster_t *c,
int peer_idx,
uint64_t committed_tag,
uint64_t committed_lsn,
uint64_t oldest_wal_lsn)
{
if (!ha || peer_idx < 0 || peer_idx >= MAX_HA_CANDIDATES)
return -EINVAL;
uint64_t standby_tag = ha->standby_reported_tag[peer_idx];
uint64_t standby_lsn = ha->standby_reported_lsn[peer_idx];
ha_repl_action_t action = ha_evaluate_repl_action(standby_tag, standby_lsn,
committed_tag, committed_lsn,
oldest_wal_lsn);
switch (action) {
case HA_REPL_PUSH_CONFIG:
INFO("HA repl: standby[%d] tag=%" PRIu64 " < committed_tag=%" PRIu64
" → push config", peer_idx, standby_tag, committed_tag);
#ifdef XCE_TESTING
if (ha_repl_push_config_for_standby_hook != NULL) {
ha_repl_push_config_for_standby_hook(c, peer_idx);
break;
}
#endif
if (c) {
#ifndef XCE_TESTING
extern int conf_read_raw(const char *, uint8_t **, size_t *);
uint8_t *cfg_bytes = NULL;
size_t cfg_len = 0;
if (c->myconf && c->myconf->conffile &&
conf_read_raw(c->myconf->conffile, &cfg_bytes, &cfg_len) == 0) {
push_config_to_standbys(c, cfg_bytes, cfg_len,
c->myconf->config_sha256,
c->myconf->config_version_tag);
free(cfg_bytes);
} else {
push_config_to_standbys(c, NULL, 0, NULL,
c->myconf ? c->myconf->config_version_tag : 0);
}
#endif
}
break;
case HA_REPL_SEND_SNAPSHOT:
/* Task 3.4: snapshot transfer — stub */
INFO("HA repl: standby[%d] lsn=%" PRIu64
" < oldest_wal_lsn=%" PRIu64 " → snapshot (stub)",
peer_idx, standby_lsn, oldest_wal_lsn);
break;
case HA_REPL_STREAM_WAL:
/* Task 3.5: WAL streaming — stub */
INFO("HA repl: standby[%d] lsn=%" PRIu64
" < committed_lsn=%" PRIu64 " → stream WAL (stub)",
peer_idx, standby_lsn, committed_lsn);
break;
case HA_REPL_CONVERGED:
/* No action needed — standby is up to date */
break;
}
return 0;
}
/* ── Dispatch gate (req 1.3, 1.4) ──────────────────────────────────────────
*
* Called from myreq_handler() in cluster.c before the opcode switch.
@ -1537,6 +1623,15 @@ int (*ha_push_config_hook)(void *c) = NULL;
*/
int (*ha_nop_req_hook)(endpoint_t *ep) = 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).
* When non-NULL, ha_dispatch_repl_action_for_standby() calls this INSTEAD OF
* push_config_to_standbys() when the decision is HA_REPL_PUSH_CONFIG.
* Requirements: 1.4 (ha-repl-hardening)
*/
int (*ha_repl_push_config_for_standby_hook)(void *c, int peer_idx) = NULL;
#endif /* XCE_TESTING */
/* ── Election Evaluation (Req 2.1, 2.7, 2.8) ───────────────────────────────
@ -2114,6 +2209,17 @@ static int repl_ack_decode_and_dispatch(endpoint_t *ep, vote_ep_ctx_t *vctx)
ha_process_standby_state(ha, peer_idx,
sr.config_tag, sr.applied_lsn,
committed_tag, committed_lsn);
/* Task 3.3: run the unified decision loop for this standby.
* oldest_wal_lsn=0 until snapshot transfer is wired (Task 3.4). */
uint64_t oldest_wal_lsn = 0;
#ifndef XCE_TESTING
if (c)
oldest_wal_lsn = 0; /* Task 3.4: wire persist->oldest_wal_lsn */
#endif
ha_dispatch_repl_action_for_standby(ha, c, peer_idx,
committed_tag, committed_lsn,
oldest_wal_lsn);
} else {
capn_free(&capn_ctx);
}