From 6ea89569fa3fa75960b0d0130e65b82d791f7f0e Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Sun, 21 Jun 2026 15:40:26 +0000 Subject: [PATCH] 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. --- .kiro/specs/raft-log-replication/tasks.md | 2 +- src/lib/raft/raft.c | 12 ++++ src/lib/testsuite/test_raft.c | 74 +++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/.kiro/specs/raft-log-replication/tasks.md b/.kiro/specs/raft-log-replication/tasks.md index c495da27..e1fe7a44 100644 --- a/.kiro/specs/raft-log-replication/tasks.md +++ b/.kiro/specs/raft-log-replication/tasks.md @@ -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 diff --git a/src/lib/raft/raft.c b/src/lib/raft/raft.c index 19b7874b..0382ec1e 100644 --- a/src/lib/raft/raft.c +++ b/src/lib/raft/raft.c @@ -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) diff --git a/src/lib/testsuite/test_raft.c b/src/lib/testsuite/test_raft.c index 8c00fc46..c6678013 100644 --- a/src/lib/testsuite/test_raft.c +++ b/src/lib/testsuite/test_raft.c @@ -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);