feat: ha_do_promote/demote + SIGUSR1 atomic flag handler (P1-3a)
- ha_do_promote(): atomic_fetch_add term, set role=PRIMARY, reset last_received_crc to WAL_CRC_NONE (COH-5 chain restart on promotion) - ha_do_demote(): set role=STANDBY, reset hooks_registered idempotency guard - ha_install_sigusr1(): sigaction handler stores only atomic int — async-signal-safe per POSIX.1-2017 §2.4.3; global g_sigusr1_ha set once before signals are enabled - ha_promote_to_primary() / ha_demote_to_standby() remain (void)c stubs pending P1-5 cluster wiring Tests: 3 new cases in test_ha_role.c test_promote_increments_term: term > term_before, role == PRIMARY test_demote_sets_standby_role: role reverts to STANDBY after promote+demote test_sigusr1_sets_failover_requested: raise(SIGUSR1) sets atomic flag Result: 100 Tests 0 Failures 0 Ignored (controller suite)
This commit is contained in:
parent
2fae4c3df5
commit
652b015458
3 changed files with 165 additions and 0 deletions
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -37,6 +38,62 @@ int ha_init(ha_state_t *ha, const ha_config_t *cfg, int my_priority)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* ── P1-3a: promotion / demotion / SIGUSR1 ─────────────────── */
|
||||
|
||||
/*
|
||||
* Async-signal-safe: the handler only performs an atomic store on an
|
||||
* int-width type, which is async-signal-safe on all POSIX platforms
|
||||
* (POSIX.1-2017 §2.4.3). The pointer is set once by ha_install_sigusr1()
|
||||
* before signals are enabled; no locking is required.
|
||||
*/
|
||||
static ha_state_t *g_sigusr1_ha = NULL;
|
||||
|
||||
static void sigusr1_handler(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
if (g_sigusr1_ha)
|
||||
atomic_store_explicit(&g_sigusr1_ha->failover_requested, 1,
|
||||
memory_order_release);
|
||||
}
|
||||
|
||||
void ha_install_sigusr1(ha_state_t *ha)
|
||||
{
|
||||
g_sigusr1_ha = ha;
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = sigusr1_handler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART;
|
||||
sigaction(SIGUSR1, &sa, NULL);
|
||||
}
|
||||
|
||||
void ha_do_promote(ha_state_t *ha)
|
||||
{
|
||||
/* Step 1: Increment election term — monotonically increasing fencing token */
|
||||
atomic_fetch_add_explicit(&ha->term, 1, memory_order_acq_rel);
|
||||
|
||||
/* Step 2: Set role to PRIMARY */
|
||||
atomic_store_explicit(&ha->role, (int32_t)CTRL_PRIMARY, memory_order_release);
|
||||
|
||||
/* Step 3: Reset last_received_crc chain (COH-5 — new primary starts fresh chain) */
|
||||
ha->last_received_crc = WAL_CRC_NONE;
|
||||
|
||||
/* Steps 4+: hook re-registration, standby heartbeat, cluster_start() —
|
||||
* deferred to ha_promote_to_primary(cluster_t*) in P1-5. */
|
||||
}
|
||||
|
||||
void ha_do_demote(ha_state_t *ha)
|
||||
{
|
||||
/* Step 1: Set role to STANDBY — vcpu threads will reject new primary ops */
|
||||
atomic_store_explicit(&ha->role, (int32_t)CTRL_STANDBY, memory_order_release);
|
||||
|
||||
/* Step 2: Reset hook idempotency guard — next promote will re-register */
|
||||
ha->hooks_registered = 0;
|
||||
|
||||
/* Step 3: hook disable done in ha_demote_to_standby(cluster_t*) in P1-5
|
||||
* because it needs the cluster's hooksys_t. */
|
||||
}
|
||||
|
||||
/* ── Watchdog thread implementation ────────────────────────── */
|
||||
|
||||
void ha_check_heartbeat_nocluster(ha_state_t *ha)
|
||||
|
|
|
|||
|
|
@ -97,6 +97,29 @@ int ha_promote_to_primary(cluster_t *c);
|
|||
*/
|
||||
int ha_demote_to_standby(cluster_t *c);
|
||||
|
||||
/* ── Internal promotion/demotion (P1-3a) ───────────────────── */
|
||||
|
||||
/*
|
||||
* ha_do_promote: increment term, set role=PRIMARY, reset last_received_crc.
|
||||
* No cluster_t dependency — fully testable standalone.
|
||||
* Callers: ha_promote_to_primary() (P1-5), watchdog failover path.
|
||||
*/
|
||||
void ha_do_promote(ha_state_t *ha);
|
||||
|
||||
/*
|
||||
* ha_do_demote: set role=STANDBY, reset hooks_registered idempotency guard.
|
||||
* Hook disable requires cluster_t and is done in ha_demote_to_standby() (P1-5).
|
||||
*/
|
||||
void ha_do_demote(ha_state_t *ha);
|
||||
|
||||
/*
|
||||
* ha_install_sigusr1: register a SIGUSR1 handler that atomically sets
|
||||
* ha->failover_requested. The handler uses only async-signal-safe operations
|
||||
* (atomic store on an int-width type — POSIX.1-2017 §2.4.3).
|
||||
* Must be called once, before any signal can arrive.
|
||||
*/
|
||||
void ha_install_sigusr1(ha_state_t *ha);
|
||||
|
||||
/* ── Watchdog thread API (P1-3) ────────────────────────────── */
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include <signal.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -151,6 +152,86 @@ static void test_heartbeat_stale_no_crash_without_primary(void)
|
|||
printf("test_heartbeat_stale_no_crash_without_primary: PASS\n");
|
||||
}
|
||||
|
||||
/* ── P1-3a tests ─────────────────────────────────────────────── */
|
||||
|
||||
/* Term must increment on each promotion attempt */
|
||||
static void test_promote_increments_term(void)
|
||||
{
|
||||
ha_state_t ha;
|
||||
ha_config_t cfg = {
|
||||
.heartbeat_interval_ms = 3000,
|
||||
.heartbeat_timeout_ms = 10000,
|
||||
.election_timeout_ms = 2000,
|
||||
.probe_connect_timeout_ms = 500,
|
||||
.max_promotion_retries = 5,
|
||||
};
|
||||
ha_init(&ha, &cfg, 0);
|
||||
uint64_t term_before = atomic_load_explicit(&ha.term, memory_order_acquire);
|
||||
|
||||
ha_do_promote(&ha); /* internal promotion logic — no cluster_t needed */
|
||||
|
||||
uint64_t term_after = atomic_load_explicit(&ha.term, memory_order_acquire);
|
||||
TEST_ASSERT_TRUE(term_after > term_before);
|
||||
TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY,
|
||||
(int)atomic_load_explicit(&ha.role, memory_order_acquire));
|
||||
|
||||
ha_fini(&ha);
|
||||
printf("test_promote_increments_term: PASS\n");
|
||||
}
|
||||
|
||||
/* Demotion must set role back to STANDBY */
|
||||
static void test_demote_sets_standby_role(void)
|
||||
{
|
||||
ha_state_t ha;
|
||||
ha_config_t cfg = {
|
||||
.heartbeat_interval_ms = 3000,
|
||||
.heartbeat_timeout_ms = 10000,
|
||||
.election_timeout_ms = 2000,
|
||||
.probe_connect_timeout_ms = 500,
|
||||
.max_promotion_retries = 5,
|
||||
};
|
||||
ha_init(&ha, &cfg, 0);
|
||||
ha_do_promote(&ha);
|
||||
TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY,
|
||||
(int)atomic_load_explicit(&ha.role, memory_order_acquire));
|
||||
|
||||
ha_do_demote(&ha);
|
||||
TEST_ASSERT_EQUAL_INT(CTRL_STANDBY,
|
||||
(int)atomic_load_explicit(&ha.role, memory_order_acquire));
|
||||
|
||||
ha_fini(&ha);
|
||||
printf("test_demote_sets_standby_role: PASS\n");
|
||||
}
|
||||
|
||||
/* SIGUSR1 must set failover_requested via async-signal-safe atomic store */
|
||||
static void test_sigusr1_sets_failover_requested(void)
|
||||
{
|
||||
ha_state_t ha;
|
||||
ha_config_t cfg = {
|
||||
.heartbeat_interval_ms = 3000,
|
||||
.heartbeat_timeout_ms = 10000,
|
||||
.election_timeout_ms = 2000,
|
||||
.probe_connect_timeout_ms = 500,
|
||||
.max_promotion_retries = 5,
|
||||
};
|
||||
ha_init(&ha, &cfg, 0);
|
||||
|
||||
/* Install real SIGUSR1 handler that uses only the atomic flag */
|
||||
ha_install_sigusr1(&ha);
|
||||
|
||||
/* Send SIGUSR1 to self */
|
||||
raise(SIGUSR1);
|
||||
|
||||
/* Handler must have set failover_requested */
|
||||
TEST_ASSERT_TRUE(
|
||||
atomic_load_explicit(&ha.failover_requested, memory_order_acquire) != 0);
|
||||
|
||||
/* Restore default SIGUSR1 handler */
|
||||
signal(SIGUSR1, SIG_DFL);
|
||||
ha_fini(&ha);
|
||||
printf("test_sigusr1_sets_failover_requested: PASS\n");
|
||||
}
|
||||
|
||||
void test_ha_role(void)
|
||||
{
|
||||
RUN_TEST(test_ha_init_default_role_is_standby);
|
||||
|
|
@ -161,4 +242,8 @@ void test_ha_role(void)
|
|||
RUN_TEST(test_watchdog_starts_and_stops);
|
||||
RUN_TEST(test_heartbeat_no_action_when_no_primary);
|
||||
RUN_TEST(test_heartbeat_stale_no_crash_without_primary);
|
||||
/* P1-3a */
|
||||
RUN_TEST(test_promote_increments_term);
|
||||
RUN_TEST(test_demote_sets_standby_role);
|
||||
RUN_TEST(test_sigusr1_sets_failover_requested);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue