feat(raft-log-replication): init leader progress on becoming leader (task 2.2)

next_index[j]=log_len+1, match_index[j]=0 for every peer at both win paths
(single-node RAFT_ELECTED_IMMEDIATELY in raft_start_election; multi-node
RAFT_BECAME_LEADER in raft_on_vote_reply) via shared raft_init_leader_progress
helper (raft.tla BecomeLeader, Req 3.1). Suite green under ASAN.
This commit is contained in:
Rongsong Shen 2026-06-21 15:40:26 +00:00
parent d70f2507c3
commit 6ea89569fa
3 changed files with 87 additions and 1 deletions

View file

@ -39,7 +39,7 @@
- _Boundary: raft.c, test_raft.c_
- _Depends: 1.3_
- [ ] 2.2 RED+GREEN: leader progress initialized on becoming leader
- [x] 2.2 RED+GREEN: leader progress initialized on becoming leader
- Test: after a node becomes leader, `next_index[j] == log_len + 1` and `match_index[j] == 0` for every peer (raft.tla BecomeLeader)
- Implement the `next_index`/`match_index` initialization on the win path (`raft_start_election` single-node + the multi-node become-leader transition)
- Observable completion: assertion on `next_index`/`match_index` after election passes; no regression in election tests

View file

@ -74,6 +74,16 @@ int raft_init(raft_state_t *s, const raft_config_t *cfg)
return 0;
}
/* Initialize per-peer leader progress on becoming leader (raft.tla BecomeLeader,
* Req 3.1): next_index[j] = log_len + 1, match_index[j] = 0 for every peer j. */
static void raft_init_leader_progress(raft_state_t *s)
{
for (int j = 0; j < s->n_peers; j++) {
s->next_index[j] = s->log_len + 1;
s->match_index[j] = 0;
}
}
int raft_start_election(raft_state_t *s)
{
/* Guard: reject if already CANDIDATE or LEADER */
@ -103,6 +113,7 @@ int raft_start_election(raft_state_t *s)
if (s->n_peers == 1) {
s->role = RAFT_LEADER;
s->election_decided = 1;
raft_init_leader_progress(s);
s->cbs->on_become_leader(s->ctx, s->term);
return RAFT_ELECTED_IMMEDIATELY;
}
@ -389,6 +400,7 @@ int raft_on_vote_reply(raft_state_t *s, int from_idx, int granted, uint64_t term
if (s->votes_granted >= quorum) {
s->role = RAFT_LEADER;
s->election_decided = 1;
raft_init_leader_progress(s);
s->cbs->on_become_leader(s->ctx, s->term);
s->cbs->on_cancel_timeout(s->ctx);
if (s->cbs->on_trace)

View file

@ -1416,6 +1416,78 @@ static void test_vote_reply_pending_waiting(void)
TEST_ASSERT_EQUAL_INT(0, g_mock.cancel_timeout_count);
}
/* ---------- leader progress init on becoming leader (task 2.2) ---------- */
/* Init a node with a non-zero restored log length (initial_log_len). */
static int init_node_log_len(raft_state_t *s, raft_callbacks_t *cbs,
int n_peers, int self_idx, uint64_t initial_log_len)
{
raft_config_t cfg;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = n_peers;
cfg.self_idx = self_idx;
cfg.election_timeout_ms = 1000;
cfg.initial_log_len = initial_log_len;
cfg.cbs = cbs;
return raft_init(s, &cfg);
}
/*
* test_leader_progress_init_single_node (raft.tla BecomeLeader, Req 3.1)
*
* Single-node immediate-win path (raft_start_election → RAFT_ELECTED_IMMEDIATELY).
* Non-zero restored log (log_len=4): on becoming leader, next_index[j] must equal
* log_len+1 (=5) and match_index[j] must equal 0 for EVERY peer j in [0, n_peers).
*/
static void test_leader_progress_init_single_node(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_mock_cbs();
reset_mock();
TEST_ASSERT_EQUAL_INT(0, init_node_log_len(&s, &cbs, 1, 0, 4));
TEST_ASSERT_EQUAL_UINT64(4, s.log_len);
int rc = raft_start_election(&s);
TEST_ASSERT_EQUAL_INT(RAFT_ELECTED_IMMEDIATELY, rc);
TEST_ASSERT_EQUAL_INT(RAFT_LEADER, s.role);
for (int j = 0; j < s.n_peers; j++) {
TEST_ASSERT_EQUAL_UINT64(s.log_len + 1, s.next_index[j]);
TEST_ASSERT_EQUAL_UINT64(0, s.match_index[j]);
}
}
/*
* test_leader_progress_init_multi_node (raft.tla BecomeLeader, Req 3.1)
*
* Multi-node become-leader path (raft_on_vote_reply → RAFT_BECAME_LEADER).
* Non-zero restored log (log_len=7): on reaching quorum, next_index[j] must equal
* log_len+1 (=8) and match_index[j] must equal 0 for EVERY peer j in [0, n_peers).
*/
static void test_leader_progress_init_multi_node(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_mock_cbs();
reset_mock();
TEST_ASSERT_EQUAL_INT(0, init_node_log_len(&s, &cbs, 3, 0, 7));
TEST_ASSERT_EQUAL_UINT64(7, s.log_len);
/* Drive to CANDIDATE then reach quorum with one peer grant. */
TEST_ASSERT_EQUAL_INT(RAFT_OK, raft_start_election(&s));
TEST_ASSERT_EQUAL_INT(RAFT_CANDIDATE, s.role);
int rc = raft_on_vote_reply(&s, 1, 1, s.term);
TEST_ASSERT_EQUAL_INT(RAFT_BECAME_LEADER, rc);
TEST_ASSERT_EQUAL_INT(RAFT_LEADER, s.role);
for (int j = 0; j < s.n_peers; j++) {
TEST_ASSERT_EQUAL_UINT64(s.log_len + 1, s.next_index[j]);
TEST_ASSERT_EQUAL_UINT64(0, s.match_index[j]);
}
}
/* ---------- raft_on_election_timeout tests (task 3.5) ---------- */
/*
@ -4010,6 +4082,8 @@ void test_raft(void)
RUN_TEST(test_vote_reply_no_quorum_all_replied_3node);
RUN_TEST(test_vote_reply_quorum_5node);
RUN_TEST(test_vote_reply_pending_waiting);
RUN_TEST(test_leader_progress_init_single_node);
RUN_TEST(test_leader_progress_init_multi_node);
/* raft_on_vote_request (task 3.3) */
RUN_TEST(test_vote_req_stale_term);