feat: heartbeat watchdog pthread + ha_check_heartbeat + start/stop API (P1-3)

- Add ha_start_watchdog() / ha_stop_watchdog() — dedicated pthread with
  nanosleep(heartbeat_interval_ms) loop; stop via atomic watchdog_stop +
  pthread_join (no pthread_cancel, safe for OpenSSL)
- Add ha_check_heartbeat_nocluster() — reads role with
  atomic_load_explicit(memory_order_acquire), guards on primary_host[0]=='\0',
  computes elapsed_ms from CLOCK_MONOTONIC, sets failover_requested on timeout
- Update ha_fini() to call ha_stop_watchdog() before mutex destroy
- 3 new tests (TDD RED→GREEN): watchdog starts/stops, no-op when no primary,
  stale heartbeat no crash; all 97 controller tests green
This commit is contained in:
Rongsong Shen 2026-05-07 14:19:27 +00:00
parent a289edb777
commit 2fae4c3df5
3 changed files with 165 additions and 1 deletions

View file

@ -97,4 +97,25 @@ int ha_promote_to_primary(cluster_t *c);
*/
int ha_demote_to_standby(cluster_t *c);
/* ── Watchdog thread API (P1-3) ────────────────────────────── */
/*
* ha_start_watchdog: spawn the heartbeat watchdog pthread.
* Must be called after ha_init(). Returns 0 on success, -errno on failure.
*/
int ha_start_watchdog(ha_state_t *ha);
/*
* ha_stop_watchdog: set watchdog_stop flag and pthread_join the thread.
* Safe to call if watchdog was never started (thread == 0).
*/
void ha_stop_watchdog(ha_state_t *ha);
/*
* ha_check_heartbeat_nocluster: check whether the primary heartbeat has
* timed out. Sets failover_requested if timeout exceeded and primary_host
* is non-empty. Test-only entry point — does not need cluster_t.
*/
void ha_check_heartbeat_nocluster(ha_state_t *ha);
#endif /* HA_H */