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:
parent
7b507156a0
commit
89b85e1680
6 changed files with 805 additions and 7 deletions
|
|
@ -61,7 +61,7 @@
|
|||
- _Requirements: 1.8, 3.2, 3.4_
|
||||
- _Boundary: Activation Fence (ha.c/ha.h)_
|
||||
|
||||
- [ ] 6.2 Gate primary request-serving and promotion on the new fence
|
||||
- [x] 6.2 Gate primary request-serving and promotion on the new fence
|
||||
- Extend the primary request-dispatch gate with this new fence check, composed alongside the existing configuration-byte fence rather than merged into it, admitting replication and read-only operations while fenced and refusing state-changing client requests
|
||||
- Extend the promotion gate the same way, so a member whose materialization is incomplete cannot become primary and immediately violate the serving gate the moment it wins an election
|
||||
- Introduce no new promotion-eligibility mechanism for WAL/log completeness — this fence is about materialization completeness only, layered on top of the existing Raft log-freshness election gate, not replacing or duplicating it
|
||||
|
|
@ -149,6 +149,8 @@
|
|||
|
||||
## Implementation Notes
|
||||
|
||||
- **Task 6.2 round-1 rejection (real bug, caught by review):** the first implementation wired the fence into both gates + a retry trampoline correctly, but `upgrade_materialization_fence` had no production path that could ever SET it from its zero-initialized default — every setter call site was gated on the fence already being set (closed loop). Fixed with two unconditional call sites: `persist.c`'s `apply_committed_entry` `WAL_CONFIG_MARKER` case now unconditionally calls `upgrade_materialization_verify(c)` right after `persist_checkpoint_marker_triggered` returns (regardless of its rc), and `myapp.c`'s boot sequence unconditionally calls it right after `config_activate_committed` (Req 5.2, crash/restart resume). New cross-module direction: `persist.c` now externs an `ha.c` function (mirrors the existing `persist.c → worker.c` extern pattern, no circular include). Lock-reentrancy re-verified safe (neither call chain touches `apply_pump_lock`).
|
||||
|
||||
- **Task 6.1 process note:** the Claude account's monthly API spend limit was hit mid-task (implementer subagent cut off after landing `ha.c`/`ha.h` but before tests/build). The orchestrator finished this task directly (no subagent implementer/reviewer pair) rather than stopping: verified the partial diff, wrote 4 tests into `test_config_repl.c` (reusing existing `t42_/t43_` fixtures rather than creating `test_upgrade_activation_fence.c` — that file is task 8.3's own boundary per design.md, not 6.1's), built, ran the full suite (1001 Tests 0 Failures 0 Ignored), and self-reviewed the diff in lieu of an independent reviewer subagent. Flagging this so a future session/reviewer knows this task's diff did not go through the normal two-subagent review gate.
|
||||
|
||||
- **Task 5 correction to design.md:** design.md's "Marker Apply Trigger" section says "Call `persist_checkpoint()` directly" — this is unsafe as written. `persist_apply_pump_run` holds `apply_pump_lock` across the whole dispatch loop (including the `apply_committed_entry` call), and `persist_checkpoint()` itself re-acquires that same non-recursive mutex — a real self-deadlock on the live pump path (primary AND standby online-apply). Separately, `last_applied_lsn`/replay position only advance AFTER `apply_committed_entry` returns at both call sites, so pinning `checkpoint_lsn` from `last_applied_lsn` inside the marker case would mislabel the checkpoint with the previous entry's LSN, not the marker's own. Fixed via `persist_checkpoint_write_locked(p, checkpoint_lsn)` (no self-lock, explicit LSN param) + `persist_checkpoint_marker_triggered(p, lsn)`, called from `apply_committed_entry`'s new `WAL_CONFIG_MARKER` case with the marker's own `lsn` parameter. design.md should be updated to reflect this; not done as part of task 5 (docs-only, deferred).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue