fix(controller): run the become-leader tail on a task class that may block (bug-1433)

A winning RequestVoteReply is dispatched through peer_ctrl_classify, which
returned VTASK_LIVENESS for all peer-control traffic -- the never-blocks,
never-admission-gated class. The reply runs raft_cb_become_leader, whose
tail persist_apply_pump_run_sync() does bounded blocking WAL pread()s (it
cannot be made async without reopening the bug-1043-family job loss on a
freshly promoted primary). The fdatasync half of bug-1433 was removed in
40f510fb; this pread was the tracked residual.

peer_ctrl_classify now returns VTASK_LIVE_BLOCK while the node is
CTRL_ELECTING -- its peer-control traffic is then vote/PreVote replies,
election work like inbound RequestVote, which already uses LIVE_BLOCK.
As PRIMARY/STANDBY the traffic is WAL/config ACKs and stays VTASK_LIVENESS,
so a quorum of acks is never admission-gated (R4.5); a candidate has no
commit to advance.

New test_peer_ctrl_classify_vote_replies_while_electing (RED before).
Controller suite 1240/0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-09-27 05:52:38 +00:00
parent 33c4f28c6a
commit 255b0faad1
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
2 changed files with 68 additions and 11 deletions

View file

@ -592,6 +592,42 @@ static void test_p3_cross_endpoint_not_serialized(void)
/* ── Suite entry point ──────────────────────────────────────────────────── */
/* bug-1433: a winning vote reply runs raft_cb_become_leader, whose tail
* persist_apply_pump_run_sync() does blocking WAL pread()s -- so while this
* node is a candidate (CTRL_ELECTING) its peer-control messages (vote
* replies) must be VTASK_LIVE_BLOCK, the class inbound RequestVote already
* uses, not the never-blocks VTASK_LIVENESS. As PRIMARY/STANDBY they are
* WAL/config ACKs and must stay VTASK_LIVENESS (never admission-gated) so
* commit can always advance. */
extern vtask_class_t ha_peer_ctrl_classify_for_test(ha_state_t *, io_packet_t *);
static void test_peer_ctrl_classify_vote_replies_while_electing(void)
{
cluster_t c;
io_packet_t pkt;
memset(&c, 0, sizeof(c));
memset(&pkt, 0, sizeof(pkt));
ha_init(&c.ha, &g_cfg, 0);
atomic_store(&c.ha.role, CTRL_ELECTING);
TEST_ASSERT_EQUAL_INT_MESSAGE(VTASK_LIVE_BLOCK,
ha_peer_ctrl_classify_for_test(&c.ha, &pkt),
"bug-1433: vote replies while electing may run the become-leader "
"tail's blocking reads -- VTASK_LIVE_BLOCK");
atomic_store(&c.ha.role, CTRL_PRIMARY);
TEST_ASSERT_EQUAL_INT_MESSAGE(VTASK_LIVENESS,
ha_peer_ctrl_classify_for_test(&c.ha, &pkt),
"ACKs to a primary must never be admission-gated");
atomic_store(&c.ha.role, CTRL_STANDBY);
TEST_ASSERT_EQUAL_INT(VTASK_LIVENESS,
ha_peer_ctrl_classify_for_test(&c.ha, &pkt));
ha_fini(&c.ha);
}
void test_ha_dispatch_integration(void)
{
UnitySetTestFile(__FILE__);
@ -600,6 +636,7 @@ void test_ha_dispatch_integration(void)
RUN_TEST(test_p1_configpush_transfer_does_not_block_cross_endpoint);
/* P2 — VTASK_LIVENESS ack runs under *_BLOCK pool saturation */
RUN_TEST(test_p2_ack_runs_under_pool_saturation);
RUN_TEST(test_peer_ctrl_classify_vote_replies_while_electing);
/* P3 — cross-endpoint peer control is not serialized */
RUN_TEST(test_p3_cross_endpoint_not_serialized);
}