fix(ha-commit-durability)!: controller never self-terminates — loud non-promotable fence replaces recovery FATAL/abort
Per design invariant (a controller exits only on crash or admin kill), replace the self-abort recovery terminal (tasks 3.3/4.3/4.4) with a loud, permanent, non-promotable fence that stays alive and keeps retrying catch-up (self-heals on peer return; no manual admin recovery). Records the never-self-terminate invariant as project-wide guidance in src/controller/CLAUDE.md + .kiro/steering/ha-async-io.md.
This commit is contained in:
parent
addf53e3b4
commit
59d9df246c
10 changed files with 607 additions and 406 deletions
|
|
@ -520,7 +520,10 @@ void ha_compute_safe_committed_lsn(ha_elect_ctx_t *ctx,
|
|||
* 2.5, 2.6). Maps the promoting member's recovery/durability state to the Boot
|
||||
* Policy Decision Table outcome by building recover_policy_inputs_t and
|
||||
* delegating to persist_recover_decision (DRY — the 3.2/3.3 decision table is
|
||||
* the single source of truth; this gate does not re-derive it):
|
||||
* the single source of truth; this gate does not re-derive it). Note the
|
||||
* FENCE_UNRECOVERABLE terminal is a LOUD PERMANENT NON-promotable fence that
|
||||
* STAYS ALIVE and keeps retrying catch-up — it is NEVER process exit
|
||||
* (never-self-exit invariant, 2026-06-18):
|
||||
* - recovery_truncated → the task-3.1 structural-corruption flag.
|
||||
* - durable_floor → the local CLAMPED durable position (read AFTER the
|
||||
* promotion-path persist_truncate_wal_above so the
|
||||
|
|
@ -547,9 +550,12 @@ recover_decision_t ha_promotion_gate_decision(bool recovery_truncated,
|
|||
* boundary regains eligibility — Req 2.6).
|
||||
* - FENCE → sets the in_catch_up fence (refuse promotion; catch up from a
|
||||
* healthy peer) and returns FENCE.
|
||||
* - FATAL → routes through persist_recover_fatal_if_unrecoverable (logs the
|
||||
* truncation boundary at ERROR then terminates; the test seam
|
||||
* persist_fatal_hook captures it) and returns FATAL.
|
||||
* - FENCE_UNRECOVERABLE → routes through
|
||||
* persist_recover_loud_fence_if_unrecoverable (logs LOUDLY at
|
||||
* ERROR + sets the sticky persist recovery_unrecoverable flag),
|
||||
* also sets the in_catch_up fence, and returns
|
||||
* FENCE_UNRECOVERABLE. The process STAYS ALIVE, non-promotable,
|
||||
* retrying catch-up — it NEVER self-exits (Req 2.4).
|
||||
* Reuses the existing in_catch_up fence — does NOT invent a parallel fence. */
|
||||
recover_decision_t ha_evaluate_promotion_fence(ha_state_t *ha,
|
||||
bool recovery_truncated,
|
||||
|
|
@ -558,34 +564,37 @@ recover_decision_t ha_evaluate_promotion_fence(ha_state_t *ha,
|
|||
bool peer_reachable,
|
||||
bool catch_up_succeeded);
|
||||
|
||||
/* ── ha-commit-durability task 4.4: bounded-window catch-up before FATAL ─────
|
||||
/* ── ha-commit-durability task 4.4: catch-up re-alert window ─────────────────
|
||||
* (never-self-exit rework 2026-06-18.)
|
||||
*
|
||||
* The promotion gate (4.3) no longer reaches the 3.3 FATAL synchronously at the
|
||||
* promotion instant. When a recovery-truncated / behind member wins an election
|
||||
* with no reachable peer AT THAT INSTANT (a transient no-peer snapshot during
|
||||
* aggressive failover), it FENCES (sets in_catch_up, does NOT become primary)
|
||||
* and arms a one-shot delaymgr timer for DEFAULT_RECOVERY_CATCHUP_WINDOW_MS.
|
||||
* The 3.3 FATAL is reached ONLY from that timer, and ONLY if the member is STILL
|
||||
* recovery-truncated/behind AND no healthy peer is reachable AND no catch-up
|
||||
* progress was made when it fires (Req 2.4). Catch-up that brings durable_floor
|
||||
* >= HWM releases the fence and cancels the timer (Req 2.6); shutdown cancels it
|
||||
* too — never a synchronous FATAL, never an indefinite fence.
|
||||
* When a recovery-truncated / behind member wins an election with no reachable
|
||||
* peer AT THAT INSTANT (a transient no-peer snapshot during aggressive
|
||||
* failover), it FENCES (sets in_catch_up, does NOT become primary) and arms a
|
||||
* delaymgr re-alert timer for DEFAULT_RECOVERY_CATCHUP_WINDOW_MS. When the
|
||||
* timer fires, if the member is STILL recovery-truncated/behind AND no healthy
|
||||
* peer is reachable AND no catch-up progress was made, it RE-EMITS the loud
|
||||
* operator alert (persist_recover_loud_fence_if_unrecoverable) and RE-ARMS —
|
||||
* the process STAYS ALIVE and keeps retrying catch-up INDEFINITELY. It NEVER
|
||||
* terminates: a controller must never self-exit as a designed response to an
|
||||
* operational condition. Catch-up that brings durable_floor >= HWM releases
|
||||
* the fence + clears the sticky flag and stops the re-alert (Req 2.6); shutdown
|
||||
* cancels it too.
|
||||
*
|
||||
* ha_promotion_fence_window_ms: the bounded catch-up window in milliseconds.
|
||||
* ha_promotion_fence_window_ms: the re-alert interval in milliseconds.
|
||||
* Defaults to DEFAULT_RECOVERY_CATCHUP_WINDOW_MS; tests override it (via
|
||||
* ha_set_promotion_fence_window_ms) to a small value so the timer fires fast. */
|
||||
uint32_t ha_promotion_fence_window_ms(void);
|
||||
|
||||
/* ha_cancel_promotion_fence_timer: cancel a pending bounded-window FATAL timer
|
||||
* (idempotent; no-op when none is armed). Called when the member catches up
|
||||
* (fence released) or on shutdown, so neither a caught-up nor a stopped member
|
||||
* ever reaches the deferred FATAL. */
|
||||
/* ha_cancel_promotion_fence_timer: cancel a pending re-alert timer (idempotent;
|
||||
* no-op when none is armed). Called when the member catches up (fence released)
|
||||
* or on shutdown, so a stopped member stops re-alerting. It never gated a
|
||||
* process exit — the timer never terminates the process. */
|
||||
void ha_cancel_promotion_fence_timer(ha_state_t *ha);
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/* Test seam: override the bounded catch-up window so the deferred-FATAL timer
|
||||
* fires deterministically fast. 0 restores the DEFAULT_RECOVERY_CATCHUP_WINDOW_MS
|
||||
* default. XCE_TESTING only — compiled out of production. */
|
||||
/* Test seam: override the re-alert window so the timer fires deterministically
|
||||
* fast. 0 restores the DEFAULT_RECOVERY_CATCHUP_WINDOW_MS default. XCE_TESTING
|
||||
* only — compiled out of production. */
|
||||
void ha_set_promotion_fence_window_ms(uint32_t ms);
|
||||
#endif
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue