feat(ha-single-leader-role): remove CTRL_PRIMARY_COMMITTING enum + remaining decls — compiler-enforced (task 5.2)
This commit is contained in:
parent
7f974e766b
commit
93dfc02717
10 changed files with 44 additions and 55 deletions
|
|
@ -372,7 +372,7 @@ static int myreq_handler(vtask_t* task)
|
|||
* raft_on_vote_request handles:
|
||||
* - stale-term deny (term < current_term)
|
||||
* - term upgrade (term > current_term → clear voted_for)
|
||||
* - role gate: RAFT_LEADER denies; COMMITTING gate in on_vote_eligible
|
||||
* - role gate: RAFT_LEADER denies (via on_vote_eligible)
|
||||
* - one-vote-per-term invariant (voted_for != -1 && voted_for != from_idx)
|
||||
* - on_persist before granting (durability guarantee)
|
||||
*
|
||||
|
|
@ -925,7 +925,6 @@ int cluster_init_phase1(cluster_t* c)
|
|||
.max_promotion_retries = HA_DEFAULT_MAX_PROMOTION_RETRIES,
|
||||
.election_jitter_max_ms = HA_DEFAULT_ELECTION_JITTER_MAX_MS,
|
||||
.election_priority_step_ms = HA_DEFAULT_ELECTION_PRIORITY_STEP_MS,
|
||||
.committing_timeout_ms = HA_DEFAULT_COMMITTING_TIMEOUT_MS,
|
||||
};
|
||||
|
||||
int my_priority = 0;
|
||||
|
|
@ -1464,8 +1463,8 @@ int cluster_run(cluster_t* c)
|
|||
* This triggers handle_ctrlhello on the receiving standby, which runs the
|
||||
* existing catch-up replication sequence (WAL streaming, config push).
|
||||
*
|
||||
* Must be called from the COMMITTING path in ha_elect_evaluate_and_signal
|
||||
* after ha->role is set to CTRL_PRIMARY_COMMITTING (Req 7.2).
|
||||
* Must be called from the post-election path in ha_elect_evaluate_and_signal
|
||||
* after ha->role is set to CTRL_PRIMARY (Req 7.2).
|
||||
*
|
||||
* Thread safety: acquires ha->lock around standby_endpoints iteration.
|
||||
* Best-effort: failures per-endpoint are logged but do not abort the loop.
|
||||
|
|
|
|||
|
|
@ -437,13 +437,12 @@ void ha_compute_safe_committed_lsn(ha_elect_ctx_t *ctx,
|
|||
}
|
||||
|
||||
/*
|
||||
* raft_cb_become_leader: node won the election — enter COMMITTING state.
|
||||
* raft_cb_become_leader: node won the election — enter CTRL_PRIMARY directly.
|
||||
*
|
||||
* Equivalent of ha_elect_evaluate_and_signal CTRL_PRIMARY path:
|
||||
* - enter CTRL_PRIMARY_COMMITTING
|
||||
* - enter CTRL_PRIMARY (no transitional state)
|
||||
* - call cluster_init_phase2 on first boot
|
||||
* - reset repl_ack_count, push config, send ctrlhello
|
||||
* - arm committing timeout watchdog
|
||||
* - push config, send ctrlhello
|
||||
*
|
||||
* ha->raft_c must be non-NULL (production path only; test mode uses noop stubs).
|
||||
*/
|
||||
|
|
@ -469,7 +468,7 @@ static void raft_cb_become_leader(void *ctx, uint64_t term)
|
|||
uint64_t safe_lsn = 0;
|
||||
uint64_t safe_tag = 0;
|
||||
ha_compute_safe_committed_lsn(ectx, &safe_lsn, &safe_tag);
|
||||
/* Store results for use in WAL truncation and COMMITTING phase.
|
||||
/* Store results for use in WAL truncation and post-election commit.
|
||||
* safe_lsn is the highest LSN on a majority; entries above it
|
||||
* may not have been committed on all nodes and must be truncated. */
|
||||
if (c) {
|
||||
|
|
@ -486,7 +485,7 @@ static void raft_cb_become_leader(void *ctx, uint64_t term)
|
|||
* Req 2.3 (ha-repl-hardening). */
|
||||
atomic_store_explicit(&ha->last_known_committed_lsn, safe_lsn,
|
||||
memory_order_release);
|
||||
/* Update committed config tag baseline for the COMMITTING decision loop */
|
||||
/* Update committed config tag baseline for the post-election decision loop */
|
||||
atomic_store_explicit(&ha->committed_config_tag, safe_tag,
|
||||
memory_order_release);
|
||||
}
|
||||
|
|
@ -520,7 +519,7 @@ static void raft_cb_become_leader(void *ctx, uint64_t term)
|
|||
", first_boot=%d)", term, is_first_boot);
|
||||
|
||||
/* Req 2.1 (ha-repl-hardening): re-initialize per-standby convergence
|
||||
* tracking for THIS committing round. Stale converged flags from a
|
||||
* tracking for THIS primary term. Stale converged flags from a
|
||||
* previous term (demote → re-elect cycle) must not satisfy the
|
||||
* majority-convergence gate before standbys re-report their state. */
|
||||
memset(ha->standby_reported_tag, 0, sizeof(ha->standby_reported_tag));
|
||||
|
|
@ -823,7 +822,7 @@ int ha_init(ha_state_t *ha, const ha_config_t *cfg, int my_priority)
|
|||
|
||||
/* ha-repl-hardening Task 1.4: per-standby convergence tracking arrays.
|
||||
* memset(ha, 0, sizeof(*ha)) above already zeroes these arrays; the explicit
|
||||
* note documents the invariant: on entry to CTRL_PRIMARY_COMMITTING every
|
||||
* note documents the invariant: on promotion to CTRL_PRIMARY every
|
||||
* standby starts as not-converged (tag=0, lsn=0, converged=false). */
|
||||
|
||||
/* heartbeat_seen starts at 0: no heartbeat seen yet.
|
||||
|
|
@ -1237,8 +1236,8 @@ ha_repl_action_t ha_evaluate_repl_action(uint64_t standby_tag,
|
|||
/*
|
||||
* ha_process_standby_state — update per-standby convergence tracking.
|
||||
*
|
||||
* Called on each heartbeat reply from a standby (CTRL_PRIMARY_COMMITTING or
|
||||
* CTRL_PRIMARY). Stores (reported_tag, reported_lsn) into the per-standby
|
||||
* Called on each heartbeat reply from a standby (CTRL_PRIMARY).
|
||||
* Stores (reported_tag, reported_lsn) into the per-standby
|
||||
* arrays, evaluates convergence, and logs INFO on the first
|
||||
* not-converged → converged transition (Req 7.2). Regression resets the flag
|
||||
* (Req 2.5).
|
||||
|
|
@ -2071,14 +2070,14 @@ int (*ha_phase2_hook)(void *c) = NULL;
|
|||
|
||||
/*
|
||||
* ha_send_ctrlhello_hook: test-only seam for ha_send_ctrlhello_to_all_standbys.
|
||||
* When non-NULL, the COMMITTING path calls this INSTEAD OF the real function
|
||||
* When non-NULL, the post-election path calls this INSTEAD OF the real function
|
||||
* (cluster.c is not linked in unit-test binary). Req 7.2.
|
||||
*/
|
||||
int (*ha_send_ctrlhello_hook)(void *c) = NULL;
|
||||
|
||||
/*
|
||||
* ha_push_config_hook: test-only seam for push_config_transport_only.
|
||||
* When non-NULL, the COMMITTING path calls this INSTEAD OF the real function
|
||||
* When non-NULL, the post-election path calls this INSTEAD OF the real function
|
||||
* (config_repl.c's push_config_transport_only needs a real cluster). Req 7.1.
|
||||
*/
|
||||
int (*ha_push_config_hook)(void *c) = NULL;
|
||||
|
|
@ -2976,7 +2975,7 @@ int election_timeout_fn_for_test(ha_state_t *ha, ha_elect_ctx_t *ctx,
|
|||
/*
|
||||
* repl_catchup_done_pkt_for_test: build a minimal io_packet_t encoding
|
||||
* Request{opcode=replCatchUpDone, body_which=nodata}.
|
||||
* Used by peer_ctrl_msg_handler tests to verify the COMMITTING dispatch path.
|
||||
* Used by peer_ctrl_msg_handler tests to verify the repl-ack dispatch path.
|
||||
*/
|
||||
io_packet_t *repl_catchup_done_pkt_for_test(void)
|
||||
{
|
||||
|
|
@ -3273,7 +3272,7 @@ void ha_start_election_async(ha_state_t *ha, cluster_t *c,
|
|||
INFO("ha_start_election_async: no local config (tag=0, n_peers=%d) — "
|
||||
"participating as voter only, not starting own election", n_peers);
|
||||
/* Transition to CTRL_STANDBY so myapp_run schedules fetch_config_from_primary.
|
||||
* The primary will push config via ctrlhello once it enters COMMITTING. */
|
||||
* The primary will push config via ctrlhello once it becomes CTRL_PRIMARY. */
|
||||
atomic_store_explicit(&ha->role, (int32_t)CTRL_STANDBY, memory_order_release);
|
||||
/* Free ctx — no vote sends will happen from this node this round. */
|
||||
atomic_store_explicit(&ha->elect_ctx, NULL, memory_order_release);
|
||||
|
|
@ -3285,8 +3284,8 @@ void ha_start_election_async(ha_state_t *ha, cluster_t *c,
|
|||
raft_start_election(&ha->raft);
|
||||
|
||||
/* For single-node, raft_start_election fires on_become_leader synchronously.
|
||||
* on_become_leader (raft_cb_become_leader) handles role → COMMITTING,
|
||||
* phase2, and committing timeout. Free the ctx here since no raft callbacks
|
||||
* on_become_leader (raft_cb_become_leader) handles role -> CTRL_PRIMARY and
|
||||
* phase2. Free the ctx here since no raft callbacks
|
||||
* will reference it after raft_start_election returns for single-node. */
|
||||
if (n_peers == 1) {
|
||||
atomic_store_explicit(&ha->elect_ctx, NULL, memory_order_release);
|
||||
|
|
|
|||
|
|
@ -60,8 +60,7 @@ typedef enum {
|
|||
CTRL_PRIMARY = 0,
|
||||
CTRL_STANDBY = 1,
|
||||
CTRL_INIT = 2, /* Startup: listener open, awaiting heartbeat or election timeout */
|
||||
CTRL_ELECTING = 3, /* Running quorum election via OP_requestVote */
|
||||
CTRL_PRIMARY_COMMITTING = 4 /* Primary waiting for quorum ACK on config commit */
|
||||
CTRL_ELECTING = 3 /* Running quorum election via OP_requestVote */
|
||||
} ctrl_role_t;
|
||||
|
||||
/* ── Election types ────────────────────────────────────────── */
|
||||
|
|
@ -105,8 +104,8 @@ typedef struct {
|
|||
* watchdog. Set by ha_start_election_async after scheduling the timeout. */
|
||||
void *timeout_ctx; /* election_timeout_ctx_t*; NULL until watchdog scheduled */
|
||||
/* Task 4.1: cluster_t* so ha_elect_evaluate_and_signal can call
|
||||
* cluster_init_phase2(c) and arm the committing timeout watchdog.
|
||||
* NULL in unit tests (c=NULL bypasses phase2 and watchdog arming). */
|
||||
* cluster_init_phase2(c).
|
||||
* NULL in unit tests (c=NULL bypasses phase2). */
|
||||
cluster_t *c;
|
||||
/* Task 6.3: candidate's own LSN/tag, stored unconditionally so that
|
||||
* ha_compute_safe_committed_lsn can use them even for single-node elections
|
||||
|
|
@ -128,7 +127,6 @@ typedef struct {
|
|||
#define HA_DEFAULT_MAX_PROMOTION_RETRIES 5
|
||||
#define HA_DEFAULT_ELECTION_JITTER_MAX_MS 200
|
||||
#define HA_DEFAULT_ELECTION_PRIORITY_STEP_MS 500
|
||||
#define HA_DEFAULT_COMMITTING_TIMEOUT_MS 60000
|
||||
|
||||
typedef struct {
|
||||
uint32_t heartbeat_interval_ms; /* Primary heartbeat send interval (min 2000 ms) */
|
||||
|
|
@ -138,7 +136,6 @@ typedef struct {
|
|||
uint32_t max_promotion_retries; /* Max consecutive failed promotion attempts */
|
||||
uint32_t election_jitter_max_ms; /* random jitter added to election timeout (default 200) */
|
||||
uint32_t election_priority_step_ms; /* extra ms per priority level (default 500) */
|
||||
uint32_t committing_timeout_ms; /* Quorum-ACK deadline for CTRL_PRIMARY_COMMITTING (default 60000) */
|
||||
} ha_config_t;
|
||||
|
||||
/* probe_ctx_t deleted — only used by am_i_primary() (election-iomgr-refactor) */
|
||||
|
|
@ -177,14 +174,12 @@ typedef struct {
|
|||
/* ── New fields added by election-iomgr-refactor spec (Task 2.1) ── */
|
||||
void *client_ssl_ctx; /* SSL_CTX* (TLS_client_method); created in myapp_run */
|
||||
int phase2_done; /* 1 after cluster_init_phase2 runs */
|
||||
_Atomic int repl_ack_count; /* count of standbys that ACKed in COMMITTING state */
|
||||
int n_peers; /* total cluster size (including self), matches raft convention */
|
||||
/* ── New fields added by raft-library spec (Task 6.1) ──────── */
|
||||
raft_state_t raft; /* Raft consensus state (wired in task 6.2) */
|
||||
/* ── New fields added by raft-library spec (Task 6.2) ──────── */
|
||||
cluster_t *raft_c; /* cluster_t* set by ha_start_election_async; used by raft callbacks */
|
||||
void *raft_election_timeout_ctx; /* election_timeout_ctx_t* for cancel (task 6.2) */
|
||||
void *committing_timeout_ctx; /* committing_timeout_ctx_t* for cancel on step-down */
|
||||
/* ── New fields added by ha-heartbeat-liveness spec (Task 1.1) ── */
|
||||
time_t last_activity; /* CLOCK_MONOTONIC seconds of last primary message; 0 = none yet */
|
||||
/* ── New fields added by ha-repl-hardening spec (Task 1.4) ──────── */
|
||||
|
|
@ -376,7 +371,7 @@ ha_repl_action_t ha_evaluate_repl_action(uint64_t standby_tag,
|
|||
* ha_process_standby_state: update per-standby convergence tracking.
|
||||
*
|
||||
* Called on each heartbeat reply from a standby while the primary is in
|
||||
* CTRL_PRIMARY_COMMITTING or CTRL_PRIMARY. Stores the standby's reported
|
||||
* CTRL_PRIMARY. Stores the standby's reported
|
||||
* (config_tag, applied_lsn) and evaluates whether the standby has converged
|
||||
* with the primary's committed state.
|
||||
*
|
||||
|
|
@ -650,7 +645,7 @@ int ha_dispatch_repl_action_for_standby(ha_state_t *ha,
|
|||
* an OP_hello CtrlHelloRequest to each connected standby.
|
||||
* This triggers the existing catch-up replication path in handle_ctrlhello
|
||||
* (Req 7.2). Defined in cluster.c; called from ha_elect_evaluate_and_signal
|
||||
* in the COMMITTING path.
|
||||
* in the post-election path.
|
||||
*/
|
||||
void ha_send_ctrlhello_to_all_standbys(cluster_t *c);
|
||||
|
||||
|
|
@ -668,7 +663,7 @@ extern int (*ha_phase2_hook)(void *c);
|
|||
|
||||
/*
|
||||
* ha_send_ctrlhello_hook: test-only seam for ha_send_ctrlhello_to_all_standbys.
|
||||
* When non-NULL, ha_elect_evaluate_and_signal() calls this in the COMMITTING
|
||||
* When non-NULL, ha_elect_evaluate_and_signal() calls this in the post-election
|
||||
* path instead of the real function (cluster.c not linked in test binary).
|
||||
* Requirements: 7.2.
|
||||
*/
|
||||
|
|
@ -676,7 +671,7 @@ extern int (*ha_send_ctrlhello_hook)(void *c);
|
|||
|
||||
/*
|
||||
* ha_push_config_hook: test-only seam for push_config_to_standbys.
|
||||
* When non-NULL, ha_elect_evaluate_and_signal() calls this in the COMMITTING
|
||||
* When non-NULL, ha_elect_evaluate_and_signal() calls this in the post-election
|
||||
* path instead of the real function. Requirements: 7.1.
|
||||
*/
|
||||
extern int (*ha_push_config_hook)(void *c);
|
||||
|
|
|
|||
|
|
@ -820,7 +820,6 @@ static ha_config_t g_fg_cfg = {
|
|||
.max_promotion_retries = 5,
|
||||
.election_jitter_max_ms = 0,
|
||||
.election_priority_step_ms = 500,
|
||||
.committing_timeout_ms = 10000,
|
||||
};
|
||||
|
||||
/* Arrange a CTRL_PRIMARY ha_state_t with n_endpoints connected standby
|
||||
|
|
|
|||
|
|
@ -840,7 +840,6 @@ static ha_config_t g_gate_cfg = {
|
|||
.max_promotion_retries = 5,
|
||||
.election_jitter_max_ms = 0,
|
||||
.election_priority_step_ms = 500,
|
||||
.committing_timeout_ms = 10000,
|
||||
};
|
||||
|
||||
/* Arrange a CTRL_PRIMARY ha_state_t with n_endpoints connected standby
|
||||
|
|
|
|||
|
|
@ -175,11 +175,11 @@ static void test_dispatcher_electing_calls_vote_reply_hook(void)
|
|||
printf("test_dispatcher_electing_calls_vote_reply_hook: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Test 2 (dispatcher transitional-leader → repl_ack count) DELETED ─────────
|
||||
/* ── Test 2 (dispatcher transitional-leader ack count) DELETED ─────────
|
||||
* ha-single-leader-role task 4.1. Subject was the deleted ack-counting
|
||||
* promotion machinery (replCatchUpDone incrementing repl_ack_count to drive a
|
||||
* transitional leader state to PRIMARY). That path is gone — nothing left to
|
||||
* verify. No shim (design §"Transitional-State Deletions").
|
||||
* promotion machinery (replCatchUpDone incrementing the per-term ACK tally to
|
||||
* drive a transitional leader state to PRIMARY). That path is gone — nothing
|
||||
* left to verify. No shim (design §"Transitional-State Deletions").
|
||||
*/
|
||||
|
||||
/* ── Test 3: dispatcher drops packet for CTRL_STANDBY ───────────────────── */
|
||||
|
|
@ -477,10 +477,10 @@ static void test_follower_passive_no_outbound_connect(void)
|
|||
printf("test_follower_passive_no_outbound_connect: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Test 10 (Task 7.3: promoted ep repl_ack promotes to PRIMARY) DELETED ─────
|
||||
/* ── Test 10 (Task 7.3: promoted ep ack promotes to PRIMARY) DELETED ─────
|
||||
* ha-single-leader-role task 4.1. Subject was the deleted ack-counting
|
||||
* promotion machinery (replCatchUpDone incrementing repl_ack_count until the
|
||||
* majority threshold promoted a transitional leader state to CTRL_PRIMARY via
|
||||
* promotion machinery (replCatchUpDone incrementing the per-term ACK tally until
|
||||
* the majority threshold promoted a transitional leader state to CTRL_PRIMARY via
|
||||
* ha_promote_to_primary).
|
||||
* The mechanism is gone — promotion is now the heartbeat decision loop +
|
||||
* per-entry majority ack. Nothing left to verify; no shim (design
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ static void test_demote_sets_standby_role(void)
|
|||
|
||||
/*
|
||||
* Verify that ha_init zeroes the new fields added for election-iomgr-refactor:
|
||||
* phase2_done, repl_ack_count, n_peers, client_ssl_ctx
|
||||
* phase2_done, n_peers, client_ssl_ctx
|
||||
* and that the surviving leader role values are distinct in ctrl_role_t.
|
||||
*
|
||||
* Requirements: 5.2, 8.1 (election-iomgr-refactor spec); 1.2 (ha-single-leader-role:
|
||||
|
|
@ -171,9 +171,6 @@ static void test_ha_init_zeroes_new_fields(void)
|
|||
/* phase2_done must be explicitly zero after ha_init */
|
||||
TEST_ASSERT_EQUAL_INT(0, ha.phase2_done);
|
||||
|
||||
/* repl_ack_count is _Atomic int; must be zero after ha_init */
|
||||
TEST_ASSERT_EQUAL_INT(0, atomic_load_explicit(&ha.repl_ack_count, memory_order_relaxed));
|
||||
|
||||
/* n_peers must be zero after ha_init */
|
||||
TEST_ASSERT_EQUAL_INT(0, ha.n_peers);
|
||||
|
||||
|
|
|
|||
|
|
@ -755,7 +755,7 @@ static void *lifecycle_wait_thread(void *p)
|
|||
*
|
||||
* SHARED-SEQUENCE CONTRACT PIN (GREEN now, MUST STAY GREEN — not a behavioral
|
||||
* RED carrier). After task 2.2 BOTH roads to PRIMARY (the raft become-leader
|
||||
* callback and ha_repl_ack_received's quorum path) route through this same
|
||||
* callback and the legacy quorum-ack path) route through this same
|
||||
* ha_promote_to_primary sequence, which task 2.2 augments with the noop+gate
|
||||
* tail while KEEPING hook registration. This case is therefore a CONTRACT/
|
||||
* REGRESSION pin proving the shared promotion sequence registers the persist
|
||||
|
|
@ -809,8 +809,8 @@ static void test_promotion_registers_persist_hooks(void)
|
|||
|
||||
/* ── Case 4: NO WATCHDOG — promotion arms no delaymgr task (Req 1.5, 2.3).
|
||||
*
|
||||
* The shared promotion sequence must never arm a wall-clock watchdog (the
|
||||
* deleted committing_timeout_fn). Asserted via the delaymgr task-count seam:
|
||||
* The shared promotion sequence must never arm a wall-clock promotion watchdog
|
||||
* (deleted in task 4.x). Asserted via the delaymgr task-count seam:
|
||||
* the count is unchanged across ha_promote_to_primary.
|
||||
*
|
||||
* Status note (RED vs already-GREEN): the force-promotion watchdog is armed
|
||||
|
|
@ -820,8 +820,8 @@ static void test_promotion_registers_persist_hooks(void)
|
|||
* exercises — never armed a watchdog, so this case is ALREADY GREEN today and
|
||||
* stands as a regression-pin guaranteeing task 2.2's shared sequence does not
|
||||
* introduce one. The watchdog REMOVAL from raft_cb_become_leader is covered
|
||||
* at integration scope (task 4.x deletes committing_timeout_fn and the
|
||||
* test_ha_evaluate_committing committing-timeout cases). */
|
||||
* at integration scope (task 4.x deletes the promotion-watchdog arm and the
|
||||
* transitional-state evaluation cases). */
|
||||
static void test_promotion_arms_no_delaymgr_watchdog(void)
|
||||
{
|
||||
#ifdef XCE_TESTING
|
||||
|
|
@ -1140,11 +1140,11 @@ static void test_legacy_and_raft_promotion_are_symmetric(void)
|
|||
"legacy road must arm the own-term gate at noop_lsn (Req 1.4, 2.1) — "
|
||||
"identical to the raft road's shared tail");
|
||||
|
||||
/* Symmetry axis 4: NO committing-timeout watchdog armed (Req 1.5). */
|
||||
/* Symmetry axis 4: NO promotion watchdog armed (Req 1.5). */
|
||||
int after = delaymgr_task_count(&g_lc.delaymgr);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(before, after,
|
||||
"legacy road must arm NO delaymgr watchdog (Req 1.5) — symmetric with "
|
||||
"the raft road; the committing-timeout arm is deleted from this path");
|
||||
"the raft road; the promotion-watchdog arm is deleted from this path");
|
||||
|
||||
pthread_mutex_destroy(&ctx->lock);
|
||||
sem_destroy(&ctx->done);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue