feat(ha-config-bootstrap): implement ha_wait_for_heartbeat_or_timeout (task 3.4)
- sem_timedwait on heartbeat_seen semaphore with CLOCK_REALTIME deadline - deadline = now + election_timeout_ms + xorshift64 jitter - Returns CTRL_STANDBY on heartbeat arrival (calls ha_do_demote) - Returns CTRL_ELECTING on timeout (caller drives election) - Allows OPCode_heartbeat through dispatch gate in CTRL_INIT state - 3 new unit tests: standby-on-heartbeat, electing-on-timeout, gate-allows-heartbeat - 24 Tests 0 Failures in test_ha_startup suite
This commit is contained in:
parent
72776f18ff
commit
f0029bf000
4 changed files with 202 additions and 1 deletions
|
|
@ -499,6 +499,15 @@ int ha_dispatch_gate(ha_state_t *ha, int opcode)
|
|||
return 0;
|
||||
|
||||
case CTRL_INIT:
|
||||
/* Allow election probes, config probes, AND heartbeats so the primary's
|
||||
* periodic heartbeat can reach the handler and post heartbeat_seen,
|
||||
* triggering the CTRL_INIT → CTRL_STANDBY transition (Req 1.1). */
|
||||
if (opcode == OPCode_requestVote ||
|
||||
opcode == OPCode_probeConfigTag ||
|
||||
opcode == OPCode_heartbeat)
|
||||
return 0;
|
||||
return -XCE_ERR_NOT_READY;
|
||||
|
||||
case CTRL_ELECTING:
|
||||
if (opcode == OPCode_requestVote || opcode == OPCode_probeConfigTag)
|
||||
return 0;
|
||||
|
|
@ -611,3 +620,55 @@ int ha_demote_to_standby(cluster_t *c)
|
|||
ha_do_demote(&c->ha); /* sets role=STANDBY, resets hooks_registered=0 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── HA Config Bootstrap: startup heartbeat wait (Req 1.1, 1.2) ─────────── */
|
||||
|
||||
/*
|
||||
* ha_wait_for_heartbeat_or_timeout: called from the startup path after
|
||||
* ha_init() when the node needs to discover whether a primary already exists.
|
||||
*
|
||||
* Blocks on sem_timedwait(&ha->heartbeat_seen, &deadline) where
|
||||
* deadline = now(CLOCK_REALTIME) + election_timeout_ms + jitter
|
||||
*
|
||||
* The heartbeat handler (cluster.c) posts heartbeat_seen when it receives
|
||||
* OPCode_heartbeat and ha->role == CTRL_INIT. ha_dispatch_gate() permits
|
||||
* OPCode_heartbeat in CTRL_INIT state so the packet reaches the handler.
|
||||
*
|
||||
* Returns CTRL_STANDBY — semaphore was posted (heartbeat arrived);
|
||||
* ha_do_demote() was called (role = CTRL_STANDBY).
|
||||
* Returns CTRL_ELECTING — sem_timedwait timed out (ETIMEDOUT);
|
||||
* caller is responsible for driving the election.
|
||||
*/
|
||||
ctrl_role_t ha_wait_for_heartbeat_or_timeout(ha_state_t *ha)
|
||||
{
|
||||
/* Seed jitter PRNG on first use */
|
||||
if (ha->jitter_seed == 0)
|
||||
ha_seed_jitter(&ha->jitter_seed, ha->my_priority);
|
||||
|
||||
/* Compute timeout in milliseconds: election_timeout + random jitter */
|
||||
long to_ms = (long)ha->config.election_timeout_ms;
|
||||
if (ha->config.election_jitter_max_ms > 0) {
|
||||
to_ms += (long)(xorshift64_next(&ha->jitter_seed)
|
||||
% ((uint64_t)ha->config.election_jitter_max_ms + 1));
|
||||
}
|
||||
|
||||
/* sem_timedwait requires an absolute CLOCK_REALTIME deadline */
|
||||
struct timespec deadline;
|
||||
clock_gettime(CLOCK_REALTIME, &deadline);
|
||||
deadline.tv_sec += to_ms / 1000;
|
||||
deadline.tv_nsec += (to_ms % 1000) * 1000000L;
|
||||
if (deadline.tv_nsec >= 1000000000L) {
|
||||
deadline.tv_sec++;
|
||||
deadline.tv_nsec -= 1000000000L;
|
||||
}
|
||||
|
||||
int rc = sem_timedwait(&ha->heartbeat_seen, &deadline);
|
||||
if (rc == 0) {
|
||||
/* Heartbeat arrived before the deadline — demote to standby */
|
||||
ha_do_demote(ha);
|
||||
return CTRL_STANDBY;
|
||||
}
|
||||
|
||||
/* ETIMEDOUT (or any other error): treat as election timeout */
|
||||
return CTRL_ELECTING;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue