feat(election-iomgr-refactor): extend handle_request_vote with role-based deny gate — task 3.2

This commit is contained in:
Rongsong Shen 2026-05-30 11:03:35 +00:00
parent e6135ea828
commit 6de4b3ff0e
4 changed files with 286 additions and 1 deletions

View file

@ -824,11 +824,34 @@ int ha_restore_term(ha_state_t *ha, const char *workdir)
return 0;
}
/* ── Vote Handler (Req 2.2–2.6, 9.1) ───────────────────────────────────────
/* ── ha_heartbeat_is_fresh (Req 6.2) ────────────────────────────────────────
*
* Returns 1 if the elapsed time since ha->last_heartbeat is strictly less than
* ha->config.heartbeat_timeout_ms (i.e. the primary is still alive).
* Returns 0 if the heartbeat is stale (timeout has expired).
*
* Uses CLOCK_MONOTONIC to avoid wall-clock skew.
*/
static int ha_heartbeat_is_fresh(ha_state_t *ha)
{
struct timespec now, last;
clock_gettime(CLOCK_MONOTONIC, &now);
pthread_mutex_lock(&ha->lock);
last = ha->last_heartbeat;
pthread_mutex_unlock(&ha->lock);
long ms = (now.tv_sec - last.tv_sec) * 1000L
+ (now.tv_nsec - last.tv_nsec) / 1000000L;
return ms < (long)ha->config.heartbeat_timeout_ms;
}
/* ── Vote Handler (Req 2.2–2.6, 6.1, 6.2, 6.4, 9.1) ────────────────────────
*
* handle_request_vote: process an incoming OP_requestVote RPC.
*
* Decision order (fail-fast):
* 0. role gate (Req 6.1, 6.2):
* - CTRL_PRIMARY or CTRL_PRIMARY_COMMITTING → deny
* - CTRL_STANDBY with fresh heartbeat → deny
* 1. in_catch_up → deny (Req 2.5)
* 2. req term < my term → deny (Req 2.2)
* 3. req term > my term → update term, clear voted_for (Req 2.3)
@ -847,6 +870,17 @@ int handle_request_vote(ha_state_t *ha,
uint64_t my_last_lsn,
int in_catch_up_flag)
{
/* Req 6.1, 6.2: role-based deny gate — checked before any Raft state mutation.
* PRIMARY and PRIMARY_COMMITTING never grant votes (protect the live primary).
* STANDBY with a fresh heartbeat also denies (primary is alive, no new election
* needed). CTRL_INIT and CTRL_ELECTING fall through to Raft evaluation. */
ctrl_role_t my_role = (ctrl_role_t)atomic_load_explicit(&ha->role,
memory_order_acquire);
if (my_role == CTRL_PRIMARY || my_role == CTRL_PRIMARY_COMMITTING)
return 0; /* deny — I am primary (or committing); protect against challengers */
if (my_role == CTRL_STANDBY && ha_heartbeat_is_fresh(ha))
return 0; /* deny — my primary is alive */
/* Req 2.5: catching-up standbys deny all votes */
if (in_catch_up_flag)
return 0;