fix(raft-ha-core-safety): randomize per-campaign election timeout to [T,2T] (task 5.7)
The per-campaign election watchdog was armed with a FIXED election_timeout_ms (raft_start_election -> on_schedule_timeout -> raft_cb_schedule_timeout). Under cold-connection churn a just-restarted survivor opens a fresh TCP+SSL endpoint per vote round, so its single needed grant can land a hair AFTER a constant deadline on EVERY campaign -- a resonance: the campaign times out, the term re-escalates, and the now-stale grant is dropped (DropStaleResponse). Forever. Terms climb unbounded; the freshest survivor never tallies quorum. This is the final round-4 chaotic-soak election-liveness stall. Fix (Ongaro thesis 3.4): re-randomize the watchdog per campaign to the canonical [T, 2T] interval in the HA adapter, so some campaign's deadline falls AFTER its grant RTT and the grant lands in-window. Done in raft_cb_schedule_timeout so the pure (sim-tested) raft library stays deterministic. Independent of election_jitter_max_ms (which tunes only the sub-second startup/stagger jitter) so it spreads even when jmax is small/0; never below the configured floor so genuine-loss failover is not sped up past election_timeout_ms. Preserves Election Safety (no change to term/vote/DropStaleResponse logic). new ha_jittered_election_delay() helper + 2 unit tests (RED verified: stubbing jitter to 0 fails test_5_7_campaign_timeout_is_randomized with the resonance message; PIN guards floor + modulo-by-zero). Suite: 24 Tests / 0 Failures.
This commit is contained in:
parent
2b182ecde1
commit
74784b2b18
3 changed files with 131 additions and 0 deletions
|
|
@ -449,6 +449,14 @@ static void raft_cb_schedule_timeout(void *ctx, uint32_t delay_ms)
|
|||
t->c = c;
|
||||
t->ctx = NULL; /* raft path does not use ha_elect_ctx_t */
|
||||
|
||||
/* task 5.7: randomize the per-campaign watchdog to [delay_ms, 2*delay_ms]
|
||||
* (Ongaro §3.4). The library passes a FIXED election_timeout_ms; arming the
|
||||
* watchdog at exactly that value lets a constant cold-connection grant RTT
|
||||
* land just past every deadline forever (resonance → unbounded term climb).
|
||||
* Re-randomizing here breaks the resonance without touching the pure
|
||||
* (sim-tested) library. */
|
||||
delay_ms = ha_jittered_election_delay(ha, delay_ms);
|
||||
|
||||
int rc = delay_task_ms(&c->delaymgr, (int)delay_ms, 1,
|
||||
raft_election_timeout_fn, t);
|
||||
if (rc < 0) {
|
||||
|
|
@ -6671,6 +6679,22 @@ uint32_t ha_election_stagger_ms(ha_state_t *ha)
|
|||
return delay;
|
||||
}
|
||||
|
||||
/* ha_jittered_election_delay: see ha.h. Canonical Raft randomized election
|
||||
* timeout — [base, 2*base], re-drawn each campaign — applied to the per-campaign
|
||||
* watchdog so a constant cold-connection grant RTT cannot resonate just past a
|
||||
* fixed deadline forever (task 5.7). Independent of election_jitter_max_ms and
|
||||
* never below the configured floor. */
|
||||
uint32_t ha_jittered_election_delay(ha_state_t *ha, uint32_t base)
|
||||
{
|
||||
if (!ha || base == 0)
|
||||
return base;
|
||||
if (ha->jitter_seed == 0)
|
||||
ha_seed_jitter(&ha->jitter_seed, ha->my_priority);
|
||||
uint32_t extra = (uint32_t)(xorshift64_next(&ha->jitter_seed)
|
||||
% ((uint64_t)base + 1));
|
||||
return base + extra;
|
||||
}
|
||||
|
||||
/* failover_stagger_ctx_t: carries ha/c into the delaymgr-scheduled PreVote start.
|
||||
* Freed by ha_failover_stagger_fn (one-shot). */
|
||||
typedef struct {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue