feat(ha-term-safety): restore controller build + wire vote/AE term wire fields (tasks 1.1, 1.2)

1.1: WalCatchupEntry gains term@4/prevLogIndex@5/prevLogTerm@6 (append-only).
1.2: RequestVote/PreVote gain lastLogTerm@4/lastLogIndex@5; cluster.c vote callers
fixed to the extended library arity (RESTORES xce_controller + controller_unit_test
compile+link, broken by the raft-commit-safety signature extension); ha.c emits the
candidate's last-log tuple on send.

5 minimal stub log/commit callbacks wired into s_real_cbs so raft_init's new
non-NULL requirement is satisfied — tasks 2.x replace them with real bodies; the
logOk freshness gate is inert until the term seam (1.3) + on_get_entry_term (2.2).

Pre-existing test_repl_unit.c wal_catchup -EAGAIN failures + ASAN leaks are an
overlayfs /tmp fsync env artifact (bug-850), not from this task (untouched paths;
append-only schema cannot affect the @3 entries-list decode). Build/baseline
isolation is blocked by a c-capnproto flake-hash drift — see Implementation Notes.
This commit is contained in:
Rongsong Shen 2026-06-22 03:41:49 +00:00
parent e8e41752c0
commit 004a40bb3b
9 changed files with 264 additions and 9 deletions

View file

@ -50,6 +50,12 @@ typedef struct {
uint64_t my_term;
uint64_t my_tag;
uint64_t my_lsn;
/* ha-term-safety task 1.2: candidate's own Raft (lastLogTerm, lastLogIndex)
* advertised in its RequestVote/PreVote so a voter's library logOk gate can
* compare. my_last_log_index == candidate WAL log_len; my_last_log_term ==
* term of that last entry (recovered/live WAL term). */
uint64_t my_last_log_term;
uint64_t my_last_log_index;
const char *my_name;
} vote_ep_ctx_t;
@ -148,6 +154,14 @@ static int raft_cb_send_vote_request(void *ctx, int peer_idx, uint64_t term)
/* tag and lsn come from votes[0] which holds our own values */
vctx->my_tag = (ectx->n_peers > 0) ? ectx->votes[0].my_tag : 0;
vctx->my_lsn = (ectx->n_peers > 0) ? ectx->votes[0].my_lsn : 0;
/* ha-term-safety task 1.2: advertise our Raft (lastLogTerm, lastLogIndex).
* lastLogIndex == our WAL log length (the library's log_len == WAL tip LSN);
* lastLogTerm == the recovered/live WAL term that stamps that last entry.
* NOTE: sourced directly from persist (c->persist.term / raft.log_len), NOT
* via on_get_entry_term — that callback seam is task 2.x and is unwired here
* (see CONCERNS). The formal persist_entry_term accessor is task 1.3. */
vctx->my_last_log_index = ha->raft.log_len;
vctx->my_last_log_term = c ? c->persist.term : 0;
vctx->my_name = c->myconf ? c->myconf->name : "";
/* Get a fresh endpoint */
@ -256,6 +270,10 @@ static int raft_cb_send_pre_vote_request(void *ctx, int peer_idx,
vctx->my_term = prospective_term;
vctx->my_tag = (ectx->n_peers > 0) ? ectx->votes[0].my_tag : 0;
vctx->my_lsn = (ectx->n_peers > 0) ? ectx->votes[0].my_lsn : 0;
/* ha-term-safety task 1.2: advertise our Raft (lastLogTerm, lastLogIndex)
* — identical sourcing to the real-vote send path above. */
vctx->my_last_log_index = ha->raft.log_len;
vctx->my_last_log_term = c ? c->persist.term : 0;
vctx->my_name = c->myconf ? c->myconf->name : "";
endpoint_t *ep = endpoint_get();
@ -1306,6 +1324,60 @@ static void raft_cb_trace(void *ctx, const char *event, const char *detail)
detail ? " " : "", detail ? detail : "");
}
/* ── ha-term-safety: MINIMAL log/commit callback placeholders ────────────────
*
* TODO(ha-term-safety task 2.x): REPLACE these stubs with the real bodies.
* The raft library's raft_init() now REQUIRES all five non-NULL, and
* raft_on_vote_request() internally calls on_get_entry_term for its logOk gate.
* These placeholders exist ONLY to restore the controller build (tasks 1.1/1.2);
* they perform NO persistence, NO replication, and NO truncation. See CONCERNS.
*
* on_persist_entry → task 2.1 (durable WAL append/overwrite)
* on_get_entry_term → task 2.2 (WAL entry-term lookup; persist_entry_term, 1.3)
* on_send_append_entries→ task 2.3 (AppendEntries transmit)
* on_truncate_from → task 2.4 (conflict truncation)
* on_commit_advanced → task 2.5 (commit-barrier trigger)
*
* on_get_entry_term returns 0 (== "absent") for now: with the log-replication
* path unwired, the library's local last-log term reads as 0, so the logOk gate
* grants on lastLogTerm >= 0 — i.e. it does NOT yet tighten the existing
* config_tag/last_log_lsn freshness gate already enforced in cluster.c. The real
* accessor (task 2.2/1.3) makes the gate effective. */
static int ha_cb_persist_entry_stub(void *ctx, uint64_t index, uint64_t term,
const void *entry, size_t len)
{
(void)ctx; (void)index; (void)term; (void)entry; (void)len;
return 0; /* TODO task 2.1 */
}
static uint64_t ha_cb_get_entry_term_stub(void *ctx, uint64_t index)
{
(void)ctx; (void)index;
return 0; /* TODO task 2.2: 0 == absent (logOk reads local last-log term as 0) */
}
static int ha_cb_send_append_entries_stub(void *ctx, int peer_idx,
uint64_t prev_log_index,
uint64_t prev_log_term,
uint64_t leader_commit, int n_entries)
{
(void)ctx; (void)peer_idx; (void)prev_log_index; (void)prev_log_term;
(void)leader_commit; (void)n_entries;
return 0; /* TODO task 2.3 */
}
static int ha_cb_truncate_from_stub(void *ctx, uint64_t index)
{
(void)ctx; (void)index;
return 0; /* TODO task 2.4 */
}
static void ha_cb_commit_advanced_stub(void *ctx, uint64_t commit_index)
{
(void)ctx; (void)commit_index;
/* TODO task 2.5: trigger the existing synchronous commit barrier. */
}
static const raft_callbacks_t s_real_cbs = {
.on_send_vote_request = raft_cb_send_vote_request,
.on_send_pre_vote_request = raft_cb_send_pre_vote_request,
@ -1316,6 +1388,13 @@ static const raft_callbacks_t s_real_cbs = {
.on_schedule_timeout = raft_cb_schedule_timeout,
.on_cancel_timeout = raft_cb_cancel_timeout,
.on_start_election_after_delay = raft_cb_start_election_after_delay,
/* ha-term-safety: MINIMAL placeholders to satisfy raft_init's non-NULL
* requirement — REPLACE in task 2.x (see stubs + CONCERNS). */
.on_persist_entry = ha_cb_persist_entry_stub,
.on_get_entry_term = ha_cb_get_entry_term_stub,
.on_send_append_entries = ha_cb_send_append_entries_stub,
.on_truncate_from = ha_cb_truncate_from_stub,
.on_commit_advanced = ha_cb_commit_advanced_stub,
.on_trace = raft_cb_trace,
};
@ -3191,6 +3270,9 @@ static void on_vote_connected(endpoint_t *ep)
vrq.candidate_id = (char *)(vctx->my_name ? vctx->my_name : "");
vrq.config_tag = vctx->my_tag;
vrq.last_log_lsn = vctx->my_lsn;
/* ha-term-safety task 1.2: candidate's Raft (lastLogTerm, lastLogIndex). */
vrq.last_log_term = vctx->my_last_log_term;
vrq.last_log_index = vctx->my_last_log_index;
/* Wrap in xce_request_t */
xce_request_t req;
@ -3233,6 +3315,9 @@ static void on_pre_vote_connected(endpoint_t *ep)
vrq.candidate_id = (char *)(vctx->my_name ? vctx->my_name : "");
vrq.config_tag = vctx->my_tag;
vrq.last_log_lsn = vctx->my_lsn;
/* ha-term-safety task 1.2: candidate's Raft (lastLogTerm, lastLogIndex). */
vrq.last_log_term = vctx->my_last_log_term;
vrq.last_log_index = vctx->my_last_log_index;
xce_request_t req;
memset(&req, 0, sizeof(req));