feat(raft-adapter-conformance): route post-connect vote/PreVote failures to production tally

Splits vote_ep_err_handler into a real-vote handler and a new
pre_vote_ep_err_handler, each registered at its existing (already
separate) connection-setup call site. Extracts the identical
CTRL_PRIMARY-role cleanup into a shared vote_ep_handle_promoted_disconnect
helper, behavior unchanged.

Each handler's election-phase branch now routes a post-connect failure
(RDHUP/SSL-error/remote-close after connect, before any reply) through
the same production tally the connect-time failure branches already
use (vote_reply_dispatch / pre_vote_reply_dispatch, bug-1423/1424's
fix), instead of the legacy handle_request_vote_reply that never
updated the tally. Closes the one failure window bug-1423 didn't cover.

1060 -> 1061 tests, 0 failures. PreVote-specific test coverage is
task 3.2's scope (pre_vote_reply_dispatch has no hook seam analogous
to xce_vote_reply_hook; verified only by code symmetry + full-suite
regression here).
This commit is contained in:
Rongsong Shen 2026-07-29 13:53:25 +00:00
parent 65afa04aaf
commit fce73b5b58
3 changed files with 160 additions and 33 deletions

View file

@ -654,6 +654,68 @@ static void test_bug1417_middle_index_real_vote_from_peer2_not_dropped(void)
printf("test_bug1417_middle_index_real_vote_from_peer2_not_dropped: PASS\n");
}
/* ── raft-adapter-conformance task 3.1: post-connect vote failure routing ──
*
* vote_ep_err_handler's election-phase branch (the iomgr err_handler fired
* when an already-connected outgoing vote endpoint fails/closes before a
* reply arrives) must count that outcome through vote_reply_dispatch -- the
* SAME production tally path a connect-time failure (raft_cb_send_vote_request's
* own rc<0 branches, bug-1423) or a delivered denial (vote_reply_msg_handler)
* uses -- not the legacy handle_request_vote_reply, which bypasses
* raft_on_vote_reply entirely and, critically for this test, never reaches the
* xce_vote_reply_hook seam vote_reply_dispatch checks.
*
* Test seam: xce_vote_reply_hook (checked ONLY inside vote_reply_dispatch) lets
* us observe, without a live iomgr/raft election, whether the post-connect
* failure reached the production tally path. Pre-fix, this hook is never
* invoked by vote_ep_err_handler's election branch -- proving the bug.
*/
extern int vote_ep_err_handler_for_test(endpoint_t *ep);
static void test_post_connect_vote_failure_routes_through_production_tally(void)
{
reset_reply_mock();
ha_state_t ha;
TEST_ASSERT_EQUAL_INT(0, ha_init(&ha, &g_vote_cfg, 0));
/* Election phase, not CTRL_PRIMARY -- exercises the "else" branch. */
atomic_store_explicit(&ha.role, CTRL_ELECTING, memory_order_release);
atomic_store_explicit(&ha.term, 4ULL, memory_order_release);
ha_elect_ctx_t *ctx = alloc_ctx_for_vote(2);
pthread_mutex_init(&ctx->lock, NULL);
endpoint_t ep;
make_endpoint(&ep);
/* Build a real-vote endpoint context as if raft_cb_send_vote_request had
* already connected it (on_vote_connected fired, request sent) -- then
* the connection fails before any reply arrives: iomgr invokes the
* registered err_handler on this same ep. */
vote_ep_ctx_build_for_test(&ep, &ha, ctx, /*peer_idx=*/0,
4ULL, 100ULL, 50ULL, "ctrl-a");
int rc = vote_ep_err_handler_for_test(&ep);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_INT_MESSAGE(1, g_reply_hook_calls,
"post-connect vote failure must reach vote_reply_dispatch's tally "
"(xce_vote_reply_hook), not the legacy handle_request_vote_reply path "
"which never invokes this hook");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, g_reply_granted,
"a post-connect failure must be counted as a denied vote (granted=0)");
TEST_ASSERT_EQUAL_UINT64_MESSAGE(4ULL, g_reply_term,
"the synthesized denial must carry the candidate's own term");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, g_reply_peer_idx,
"peer_idx must match the failed endpoint's vote_ep_ctx_t peer_idx");
free(ep.data);
free_ctx_for_vote(ctx);
ha_fini(&ha);
printf("test_post_connect_vote_failure_routes_through_production_tally: PASS\n");
}
/* ── Suite entry point ───────────────────────────────────────────────────── */
void test_ha_vote_send(void)
@ -670,4 +732,6 @@ void test_ha_vote_send(void)
RUN_TEST(test_vote_reply_handler_no_uaf);
/* bug-1417 */
RUN_TEST(test_bug1417_middle_index_real_vote_from_peer2_not_dropped);
/* raft-adapter-conformance task 3.1 */
RUN_TEST(test_post_connect_vote_failure_routes_through_production_tally);
}