feat(ha-convergence-recovery): quorum transition logging — CAS edge-detect + 4 call sites (task 5.2)

This commit is contained in:
Rongsong Shen 2026-06-14 06:31:25 +00:00
parent 3218d9ffaf
commit 07de4581b0
3 changed files with 84 additions and 12 deletions

View file

@ -383,21 +383,72 @@ void cluster_apply_ctrlhello_reply(cluster_t *c, uint64_t committed_lsn,
}
/*
* ha_log_quorum_transition — RED scaffold (ha-convergence-recovery task 5.1).
* ha_log_quorum_transition — write-quorum loss/restore edge logger
* (ha-convergence-recovery task 5.2, Req 8.1, 8.2, 8.3).
*
* Task 5.2 replaces this body with the real edge detector: evaluate the current
* write-quorum state (ha_has_write_quorum, ha.c), compare against ha->quorum_lost,
* and on a transition CAS-flip the flag and emit exactly one WARN with the
* connected/required counts (Req 8.1, 8.2), invoking ha_quorum_transition_log_hook
* once under XCE_TESTING. Repeated calls with no state change emit nothing
* (Req 8.3); a later loss/restore cycle logs again (edge detector, not a latch).
* Evaluate the current write-quorum state and compare it against the
* ha_state_t.quorum_lost edge-detector flag. On a TRANSITION — and only on a
* transition — atomically flip the flag via CAS and emit exactly one WARN
* carrying the connected/required member counts:
* - loss: "HA quorum lost: connected=%d required=%d" (Req 8.1)
* - restore: "HA quorum restored: connected=%d required=%d" (Req 8.2)
*
* This RED stub does NOTHING: no edge detection, no flag flip, no WARN, no hook
* invocation — so the task 5.1 quorum-transition tests FAIL until 5.2 lands.
* The flag makes the logging an edge detector, NOT a latch: repeated calls while
* the state is unchanged emit nothing (Req 8.3 — no log storms across the
* dispatch-gate backstop's per-rejection calls), but a later loss/restore cycle
* logs again. The CAS guarantees once-per-transition under concurrency: only the
* caller that actually flips the flag emits the WARN.
*
* Observability only — this never returns a status and never gates an operation
* outcome; the dispatch gate's existing -XCE_ERR_NOT_READY behavior is unchanged.
*
* connected/required mirror the ha_has_write_quorum BODY (ha.c): with the repl
* back-pointer wired, connected = repl_active_standby_count(repl) and the
* threshold required = n_peers/2 (integer division — for n_peers=3 → 1). When
* the back-pointer is NULL (legacy contexts), connected falls back to the
* standby_endpoints count, matching the gate's own fallback exactly. The quorum
* decision math is NOT changed here — this only reports what that body decides.
*/
void ha_log_quorum_transition(ha_state_t *ha)
{
(void)ha; /* RED scaffold — task 5.2 implements the edge detection. */
if (ha == NULL)
return;
/* Single-node clusters always hold quorum (ha_has_write_quorum returns true
* for n_peers <= 1); there is no transition to observe. */
if (ha->n_peers <= 1)
return;
/* Mirror ha_has_write_quorum's connected-count derivation (ha.c body). */
int connected;
if (ha->repl != NULL)
connected = repl_active_standby_count(ha->repl);
else
connected = ha->standby_endpoints ? ha->standby_endpoints->count : 0;
int required = (int)(ha->n_peers / 2);
int lost_now = (connected < required) ? 1 : 0;
/* Edge detection: only the caller that flips quorum_lost from its prior value
* to lost_now emits. CAS expected=!lost_now → desired=lost_now; if the flag
* already equals lost_now (no transition) the CAS fails and we return
* silently (Req 8.3). */
int expected = lost_now ? 0 : 1;
if (!atomic_compare_exchange_strong_explicit(
&ha->quorum_lost, &expected, lost_now,
memory_order_acq_rel, memory_order_acquire))
return; /* no edge (or another thread already logged it) — no WARN */
if (lost_now)
WARN("HA quorum lost: connected=%d required=%d", connected, required);
else
WARN("HA quorum restored: connected=%d required=%d", connected, required);
#ifdef XCE_TESTING
/* Emission-count seam (Req 8.1-8.3 observable): invoked once per WARN. */
if (ha_quorum_transition_log_hook != NULL)
ha_quorum_transition_log_hook(lost_now, connected, required);
#endif
}
#ifdef XCE_TESTING