feat(config-upgrade-snapshot): gate primary serving and promotion on the activation fence

Wires task 6.1's upgrade_materialization_verify/upgrade_materialization_fenced
into ha_dispatch_gate's CTRL_PRIMARY branch (sequential block alongside,
not merged into, the existing config_serve_fenced check -- same
replication/read-only opcode allowlist, refuses state-changing ops
with -XCE_ERR_NOT_READY while fenced) and the promotion gate
(raft_cb_become_leader refuses to become CTRL_PRIMARY while fenced,
introduces no new WAL/log-completeness eligibility mechanism, never
self-terminates). Adds a 4-function self-triggering retry trampoline
(upgrade_materialization_retry_fn/_vtask_entry/_delaymgr_trampoline/
schedule_upgrade_materialization_retry) mirroring
config_activate_retry_fn's exact shape, hooked into
ha_cb_commit_advanced_stub alongside the existing config-byte retry.

Fixes a real bug caught on review: the fence had no production path
that could ever engage it from its zero-initialized default -- every
setter call site was gated on the fence already being set (closed
loop), making the new gate checks permanently dead code. Fixed with
two unconditional upgrade_materialization_verify() call sites:
persist.c's apply_committed_entry WAL_CONFIG_MARKER case (right after
persist_checkpoint_marker_triggered returns, regardless of its rc) and
myapp.c's boot sequence (right after config_activate_committed, for
Req 5.2 crash/restart resume). New test drives the fence through the
real production trigger (a forced -EBUSY checkpoint miss on a real
marker commit+apply), not a direct verify() call, proving the
end-to-end path actually works. Task 6.2 of 19.

Requirements: 3.1, 3.2, 3.3, 3.4, 4.3, 5.5
This commit is contained in:
Rongsong Shen 2026-07-25 04:58:37 +00:00
parent 7b507156a0
commit 89b85e1680
6 changed files with 805 additions and 7 deletions

View file

@ -251,6 +251,39 @@ int myapp_run(myconf_t *myconf)
}
/* Ignore other errors — node may have no config yet (first boot/standby);
* it fetches config via the primary's push post-election (Req 7.3). */
/* config-upgrade-snapshot task 6.2 (Req 5.2): resume Activation-Fence
* verification here too, unconditionally, so a crash/restart resumes
* materialization-verification before serving rather than implicitly
* treating a fresh process's zero-initialized (unfenced) fence as
* "already verified". Sequenced AFTER config_activate_committed
* deliberately: that call is what establishes/repairs which config
* BYTES are locally active for the committed marker; only once that
* has run does the separate, sequential question
* upgrade_materialization_verify asks -- has THIS MEMBER'S OWN
* checkpoint machinery actually materialized under that marker? --
* become meaningful to (re-)evaluate. persist_recover() (step 6,
* above) has already restored c.persist.cfgref_* from the latest
* loaded checkpoint by this point, so the materialization signal
* this reads is accurate here, at boot, same as it would be after
* any later checkpoint. Same "ignore other errors, log loud only on
* the fenced case" treatment as config_activate_committed above --
* ha_dispatch_gate and the promotion gate (ha.c) are what actually
* enforce the fence from here on; this call's only job is to seed
* the correct fence value at boot instead of leaving a freshly-
* started process at its unfenced zero-default regardless of
* whether materialization genuinely completed before the crash. */
int mat_rc = upgrade_materialization_verify(&c);
if (mat_rc == -EAGAIN) {
ERROR("Pre-election upgrade materialization FENCED: this "
"member's own upgrade-snapshot materialization for the "
"committed config marker is incomplete or hash-mismatched "
"— member NON-SERVING and NON-PROMOTABLE, staying alive "
"and retrying (config-upgrade-snapshot R1.8/3.2/3.4/5.2)");
}
/* Ignore other errors/success silently — mirrors the "ignore other
* errors" handling above; ha_dispatch_gate / the promotion gate
* enforce the fence going forward. */
}
/* ── Step 8: ha_start_election_async ─────────────────────────────────── */