test(ha-convergence-recovery): RED clamped rejoin reconciliation tests + intake extraction (task 3.1)

This commit is contained in:
Rongsong Shen 2026-06-14 04:18:44 +00:00
parent 09103a3141
commit 5521720b0c
5 changed files with 300 additions and 9 deletions

View file

@ -193,6 +193,13 @@ typedef struct {
* repl_active_standby_count (ep != NULL && !in_catch_up);
* NULL → legacy standby_endpoints->count fallback.
* (Req 3.1-3.3, 6.1, 6.2; wired at init by task 5.3.) */
/* ── New field added by ha-convergence-recovery spec (Task 3.x) ──────── */
_Atomic int rejoin_reconciled; /* Defensive once-guard for rejoin
* reconciliation (Req 2.1): set after the
* first truncate+self-restart so a second
* CtrlHelloReply in the same process
* lifetime cannot re-fire the restart.
* 0 at init (cleared by ha_init memset). */
} ha_state_t;
/* ── Public API ────────────────────────────────────────────── */
@ -293,6 +300,44 @@ int ha_schedule_demotion_convergence(cluster_t *c);
int ha_demotion_convergence_fn(void *arg);
#endif
/*
* ha_rejoin_reconcile: converge a rejoining node whose local WAL log runs ahead
* of the cluster's committed boundary (ha-convergence-recovery Req 2.1–2.3,
* design "Rejoin Reconciliation", research D5).
*
* Called from the standby's CtrlHelloReply intake (cluster.c) after the existing
* last_known_committed_lsn store, with primary_committed_lsn = the boundary the
* primary reported.
*
* Contract (task 3.2 GREEN implements; task 3.1 ships a RED no-op stub):
* - local_tip = persist.current_lsn - 1.
* - If local_tip > primary_committed_lsn AND the rejoin_reconciled once-guard
* is clear: cutoff = max(primary_committed_lsn, local committed_lsn) — the
* CLAMP that never discards the locally known committed prefix (Req 2.2) —
* persist_truncate_wal_above(cutoff), WARN the discarded range, set the
* once-guard, then ha_self_restart (deferred to delaymgr if the intake runs
* on iomgr, same rule as demotion). Returns the restart result.
* - Else (tip <= boundary): return 0 with NO side effects (Req 2.3).
* - A second call in the same process lifetime takes no restart (Req 2.1 —
* defensive once-guard; "once" is also structural since post-restart the tip
* equals the boundary).
*
* Generalizes the RDHUP truncation pattern (ha_on_primary_ctrl_fd_rdhup), which
* remains in place unchanged for the failover path.
*/
int ha_rejoin_reconcile(cluster_t *c, uint64_t primary_committed_lsn);
/*
* cluster_apply_ctrlhello_reply: standby-side CtrlHelloReply intake body
* (ha-convergence-recovery task 3.1). Stores the primary-reported committed
* boundary + config tag; called from cluster.c's myreq_handler on every
* CtrlHelloReply. Defined in ha_convergence.c (a unit-linkable TU) so the
* rejoin intake-integration test can drive the real path. Task 3.2 adds the
* ha_rejoin_reconcile trigger after the store.
*/
void cluster_apply_ctrlhello_reply(cluster_t *c, uint64_t committed_lsn,
uint64_t config_tag);
/* ── Redirect helpers used by join.c ──────────────────────── */