feat(raft-commit-safety): commit_index state + on_commit_advanced callback + init (tasks 1.1, 1.2, 2.1)

1.1: raft_state_t.commit_index (monotonic, ==WAL LSN, <=log_len, advanced only via
raft_advance_commit, untouched by AE reply) + raft_config_t.initial_commit_index.
1.2: on_commit_advanced(ctx, commit_index) REQUIRED callback — fired only on strict
advance, a TRIGGER for the controller commit barrier (never does durability itself).
2.1: raft_init requires it (-EINVAL if NULL) + restores commit_index from
initial_commit_index. raft_advance_commit prototype declared (impl in 3.2).

Coupled callback wiring to keep the required-callback ripple green: sim wires
sim_on_commit_advanced (+per-node commit_index mirror & fire-count), all test cbs
builders set it, raft_sim_restart_node restores the durable commit position. Vote
signatures unchanged; no commit-advance logic yet. ASAN green (224 tests).
This commit is contained in:
Rongsong Shen 2026-06-21 23:11:17 +00:00
parent 3d6dc9cc56
commit 3ba6f03a9e
5 changed files with 170 additions and 7 deletions

View file

@ -77,6 +77,13 @@ static int stub_truncate_from(void *ctx, uint64_t index)
return 0;
}
/* ---- commit-advance callback stub (raft-commit-safety) ---- */
static void stub_commit_advanced(void *ctx, uint64_t commit_index)
{
(void)ctx; (void)commit_index;
}
/* Build a fully-populated callbacks struct */
static raft_callbacks_t make_full_cbs(void)
{
@ -93,6 +100,7 @@ static raft_callbacks_t make_full_cbs(void)
cbs.on_get_entry_term = stub_get_entry_term;
cbs.on_send_append_entries = stub_send_append_entries;
cbs.on_truncate_from = stub_truncate_from;
cbs.on_commit_advanced = stub_commit_advanced;
/* on_vote_eligible and on_trace left NULL (optional) */
return cbs;
}
@ -263,6 +271,7 @@ static raft_callbacks_t make_mock_cbs(void)
cbs.on_get_entry_term = stub_get_entry_term;
cbs.on_send_append_entries = stub_send_append_entries;
cbs.on_truncate_from = stub_truncate_from;
cbs.on_commit_advanced = stub_commit_advanced;
/* on_vote_eligible and on_trace left NULL — tests that need them set explicitly */
return cbs;
}
@ -482,6 +491,74 @@ static void test_raft_init_log_callbacks_present_succeeds(void)
TEST_ASSERT_EQUAL_UINT64(5, s.log_len);
}
/* ---- commit-advance required callback + commit_index restore (raft-commit-safety
* task 2.1, Req 2.5, 1.1) ---- */
/* on_commit_advanced NULL → -EINVAL (Req 2.5), like the other required callbacks */
static void test_raft_init_null_commit_advanced_returns_einval(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_full_cbs();
raft_config_t cfg;
memset(&s, 0, sizeof(s));
cbs.on_commit_advanced = NULL; /* knock out the new required callback */
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
/* on_commit_advanced present → succeeds (Req 2.5) */
static void test_raft_init_commit_advanced_present_succeeds(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_full_cbs();
raft_config_t cfg;
memset(&s, 0, sizeof(s));
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(0, raft_init(&s, &cfg));
}
/* commit_index restored from initial_commit_index at init (Req 1.1) */
static void test_raft_init_restores_commit_index(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_full_cbs();
raft_config_t cfg;
memset(&s, 0xAB, sizeof(s)); /* poison: prove init writes the field */
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 3;
cfg.self_idx = 0;
cfg.initial_commit_index = 7; /* committed LSN restored from disk */
cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(0, raft_init(&s, &cfg));
TEST_ASSERT_EQUAL_UINT64(7, s.commit_index);
}
/* default commit_index is 0 on first boot (Req 1.1, default 0) */
static void test_raft_init_commit_index_default_zero(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_full_cbs();
raft_config_t cfg;
memset(&s, 0xAB, sizeof(s)); /* poison */
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 3;
cfg.self_idx = 0;
cfg.cbs = &cbs; /* initial_commit_index left 0 */
TEST_ASSERT_EQUAL_INT(0, raft_init(&s, &cfg));
TEST_ASSERT_EQUAL_UINT64(0, s.commit_index);
}
/* n_peers even (>1) → -EINVAL: even-sized clusters have no fault-tolerance
* advantage over the next-smaller odd cluster; reject to prevent misconfiguration. */
static void test_raft_init_even_peers_returns_einval(void)
@ -5808,6 +5885,10 @@ void test_raft(void)
RUN_TEST(test_raft_init_null_send_append_entries_returns_einval);
RUN_TEST(test_raft_init_null_truncate_from_returns_einval);
RUN_TEST(test_raft_init_log_callbacks_present_succeeds);
RUN_TEST(test_raft_init_null_commit_advanced_returns_einval);
RUN_TEST(test_raft_init_commit_advanced_present_succeeds);
RUN_TEST(test_raft_init_restores_commit_index);
RUN_TEST(test_raft_init_commit_index_default_zero);
RUN_TEST(test_raft_init_even_peers_returns_einval);
RUN_TEST(test_raft_init_oversize_peers_returns_einval);
RUN_TEST(test_raft_init_zero_peers_returns_einval);