forked from lei.chen/myxce
feat(ha-prevote): route preVoteReply over real transport to the candidate tally (task 5.1)
peer_ctrl_msg_handler gains a CTRL_STANDBY + pre_vote_in_progress branch that decodes an incoming preVoteReply (no term adoption) and feeds it to pre_vote_reply_dispatch with the absolute peer index, paralleling the real-vote reply path. Gives pre_vote_reply_dispatch its production caller (drops the used attribute). STANDBY-no-round still drops; ELECTING/PRIMARY routing unchanged. PreVote now drives rounds to a decision end-to-end over the wire. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b765fc4705
commit
6d22f2eb1a
3 changed files with 193 additions and 5 deletions
|
|
@ -63,6 +63,33 @@ extern void ha_prevote_round_timeout_fire_for_test(ha_state_t *ha, cluster_t *c)
|
|||
* LOST outcome without a live delaymgr. */
|
||||
extern int (*ha_prevote_rearm_liveness_hook)(ha_state_t *ha, cluster_t *c);
|
||||
|
||||
/* ── Task 5.1 transport-route seams ──────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* pre_vote_reply_msg_handler_for_test: drive a preVoteReply through the REAL
|
||||
* peer_ctrl_msg_handler decode/route path while CTRL_STANDBY + round in
|
||||
* progress (the production condition). Proves the reply is NOT dropped and
|
||||
* reaches pre_vote_reply_dispatch — end-to-end, not via the test hook.
|
||||
*/
|
||||
extern int pre_vote_reply_msg_handler_for_test(endpoint_t *ep, io_packet_t *pkt);
|
||||
|
||||
/* vote_reply_build_pkt_for_test: encode a RequestVoteReply capnp packet (the
|
||||
* preVoteReply reuses the identical payload / union arm). */
|
||||
extern io_packet_t *vote_reply_build_pkt_for_test(uint64_t term, int vote_granted,
|
||||
uint64_t peer_tag,
|
||||
uint64_t peer_lsn);
|
||||
|
||||
/* vote_ep_ctx_build_for_test: allocate a vote_ep_ctx_t and wire ep->data with
|
||||
* the ABSOLUTE peer_idx (the index pre_vote_reply_dispatch expects). */
|
||||
extern endpoint_t *vote_ep_ctx_build_for_test(endpoint_t *ep,
|
||||
ha_state_t *ha,
|
||||
ha_elect_ctx_t *ctx,
|
||||
int peer_idx,
|
||||
uint64_t my_term,
|
||||
uint64_t my_tag,
|
||||
uint64_t my_lsn,
|
||||
const char *my_name);
|
||||
|
||||
/* ── Test config ─────────────────────────────────────────────────────────── */
|
||||
|
||||
static ha_config_t g_cfg = {
|
||||
|
|
@ -323,6 +350,61 @@ static void test_prevote_pending_is_noop(void)
|
|||
printf("test_prevote_pending_is_noop: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Test 7: a preVoteReply over the REAL transport reaches the tally (5.1) ─ */
|
||||
|
||||
/*
|
||||
* Task 5.1 (Req 7.1): a preVoteReply arriving on the candidate's outbound
|
||||
* PreVote endpoint — while the candidate is still CTRL_STANDBY with a round in
|
||||
* progress — must be ROUTED to the PreVote tally by peer_ctrl_msg_handler (NOT
|
||||
* dropped because the role is STANDBY), and drive the round to a decision
|
||||
* end-to-end. This exercises the production decode/route path, not the
|
||||
* pre_vote_reply_dispatch_for_test hook.
|
||||
*/
|
||||
static void test_prevote_reply_over_real_transport_reaches_tally(void)
|
||||
{
|
||||
ha_state_t ha;
|
||||
TEST_ASSERT_EQUAL_INT(0, ha_init(&ha, &g_cfg, 0));
|
||||
ha_do_demote(&ha); /* role = CTRL_STANDBY */
|
||||
make_three_node(&ha);
|
||||
|
||||
/* Start a real round: self-grant + in-progress, role stays CTRL_STANDBY. */
|
||||
ha_start_prevote_async(&ha, NULL);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(CTRL_STANDBY,
|
||||
(int)atomic_load_explicit(&ha.role, memory_order_acquire),
|
||||
"precondition: role must be CTRL_STANDBY during the round");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(1, ha.raft.pre_vote_in_progress,
|
||||
"precondition: round must be in progress");
|
||||
|
||||
/* Build an endpoint carrying ABSOLUTE peer_idx = 1 (the index the tally
|
||||
* dedups by), exactly as ha_send_pre_vote_request sets it. */
|
||||
endpoint_t ep;
|
||||
memset(&ep, 0, sizeof(ep));
|
||||
vote_ep_ctx_build_for_test(&ep, &ha, NULL, /*peer_idx=*/1,
|
||||
/*my_term=*/1, /*my_tag=*/0, /*my_lsn=*/0, "test");
|
||||
|
||||
/* Encode a GRANT preVoteReply (same wire payload as a real-vote reply). */
|
||||
io_packet_t *pkt = vote_reply_build_pkt_for_test(/*term=*/1, /*granted=*/1,
|
||||
/*tag=*/0, /*lsn=*/0);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(pkt, "vote_reply_build_pkt_for_test must succeed");
|
||||
|
||||
/* Route it through the REAL handler (peer_ctrl_msg_handler) while STANDBY. */
|
||||
int rc = pre_vote_reply_msg_handler_for_test(&ep, pkt);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "handler must return 0");
|
||||
|
||||
/* Peer grant → quorum (2 of 3) → WON: the reply was NOT dropped; it reached
|
||||
* the tally and drove the round to a decision (real-election sequence). */
|
||||
int role = (int)atomic_load_explicit(&ha.role, memory_order_acquire);
|
||||
TEST_ASSERT_TRUE_MESSAGE(role == CTRL_ELECTING || role == CTRL_PRIMARY,
|
||||
"Req 7.1: a preVoteReply over the real transport must reach the tally "
|
||||
"and drive the round to a decision (left CTRL_STANDBY)");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, ha.raft.pre_vote_in_progress,
|
||||
"the decided round must clear the in-progress flag");
|
||||
|
||||
free(ep.data); /* vctx allocated by vote_ep_ctx_build_for_test */
|
||||
ha_fini(&ha);
|
||||
printf("test_prevote_reply_over_real_transport_reaches_tally: PASS (role=%d)\n", role);
|
||||
}
|
||||
|
||||
/* ── Suite entry point ──────────────────────────────────────────────────── */
|
||||
|
||||
void test_ha_prevote(void)
|
||||
|
|
@ -334,5 +416,6 @@ void test_ha_prevote(void)
|
|||
RUN_TEST(test_prevote_round_timeout_is_loss);
|
||||
RUN_TEST(test_prevote_reusable_after_loss);
|
||||
RUN_TEST(test_prevote_pending_is_noop);
|
||||
RUN_TEST(test_prevote_reply_over_real_transport_reaches_tally);
|
||||
UNITY_END();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue