feat(ha-convergence-recovery): self-restart primitive + demotion convergence (task 2.2)

This commit is contained in:
Rongsong Shen 2026-06-14 03:47:12 +00:00
parent c8e55947c2
commit 09103a3141
7 changed files with 337 additions and 43 deletions

View file

@ -662,11 +662,53 @@ static void raft_cb_become_follower(void *ctx, uint64_t term, int leader_idx)
}
}
/* ha-convergence-recovery Req 1.1-1.3, 1.5: demotion convergence.
*
* Read the PRIOR role BEFORE the CTRL_STANDBY store below. If this node was
* the PRIMARY, it may hold an uncommitted, potentially divergent WAL suffix
* and provisional in-memory state. Synchronously disable the three
* persistence hooks HERE (before any further event can be persisted — Req
* 1.1, reusing ha_demote_to_standby's disable sequence), then schedule a
* one-shot delaymgr task that truncates above committed_lsn and self-restarts
* (Req 1.2/1.3/1.5) off this callback thread (D1: no file I/O or exec on the
* iomgr/raft callback thread).
*
* Non-PRIMARY demotions (STANDBY/INIT/ELECTING) take the unchanged path —
* they have no registered hooks and no provisional suffix to discard, so
* their behavior is byte-equivalent to before this spec. */
ctrl_role_t prior_role = (ctrl_role_t)atomic_load_explicit(
&ha->role, memory_order_acquire);
if (prior_role == CTRL_PRIMARY && c != NULL) {
/* Req 1.1: disable persistence hooks SYNCHRONOUSLY, before the role
* store and before scheduling, so no provisional entry can be produced
* after the higher term was observed. Mirrors ha_demote_to_standby
* (ha.c) — disable each hook, then clear the idempotency guard. */
if (ha->hooks_registered) {
xce_disable_hook(&c->hooksys, XCE_CONTROLLER_HOOK_JOB,
(hook_fn_t)persist_job_hook_fun);
xce_disable_hook(&c->hooksys, XCE_CONTROLLER_HOOK_QUEUE,
(hook_fn_t)persist_queue_hook_fun);
xce_disable_hook(&c->hooksys, XCE_CONTROLLER_HOOK_WORKER,
(hook_fn_t)persist_worker_hook_fun);
ha->hooks_registered = 0;
}
}
atomic_store_explicit(&ha->role, (int32_t)CTRL_STANDBY,
memory_order_release);
INFO("HA FSM (raft): → CTRL_STANDBY (term=%" PRIu64 ", first_boot=%d)",
term, is_first_boot);
/* Req 1.2/1.3/1.5: schedule the deferred truncate-above-committed-LSN +
* self-restart now that hooks are disabled and the role is STANDBY. Only
* the was-PRIMARY edge converges; all other edges skip this entirely. */
if (prior_role == CTRL_PRIMARY && c != NULL) {
WARN("HA: PRIMARY demoted (term=%" PRIu64 ") — scheduling demotion "
"convergence (truncate above committed_lsn + self-restart)", term);
(void)ha_schedule_demotion_convergence(c);
}
/* Arm heartbeat watchdog — fires ha_on_primary_ctrl_fd_rdhup if primary
* sends no ctrlhello within election_timeout_ms. */
ha_arm_heartbeat_timeout_vtask(ha, c);