fix(ha-term-safety): remove task-2.4 commit min-ceiling (Option A) + fix bug-991 catch-up off-by-one

Two coupled fixes for a live commit-stall (committed_lsn pinned at the own-term
NOOP while a 3-node cluster was durable to LSN 5 — jobs acked but never committed):

bug-991: the WAL catch-up applied_lsn was advanced to lsn+1 (next-to-apply) in
the three live/catch-up apply paths, but recovery/checkpoint/init and the leader's
catch-up streamer treat it as last-held. The over-advertise made the streamer skip
an LSN -> library logOk wedge -> no WalAck. Fixed to last-held (= lsn).

Option A (design reconsideration of REVIEW ISSUE 3): removed the
committed = min(durable-majority, raft_commit_index) ceiling. Under single-writer
the durable-majority watermark is already (term,lsn)-current-term-safe (term-carrying
replication + (term,lsn) reconcile/truncate-before-durable + own-term NOOP gate +
post-election truncate-to-safe), so the library commit_index was a redundant,
starvation-prone gate that pinned committed below the safe durable majority whenever
the library lagged. The durable-majority barrier + own-term writable gate are now
authoritative; raft_commit_index is inert-advisory.

Safety: reviewer-verified the durable-majority watermark is (term,lsn)-safe and
Figure-8 holds via the own-term gate (new RED->GREEN test); bug-851 cannot recur.
Live: commit trace 4->5 + walcompare committed-prefix identical across 3 nodes.
177 unit tests pass.
This commit is contained in:
Rongsong Shen 2026-06-23 00:21:06 +00:00
parent df4bc4f86a
commit 83fbed2370
5 changed files with 396 additions and 172 deletions

View file

@ -31,14 +31,14 @@ typedef struct commit_engine {
int n_vcpus;
int inflight_waits; /* <= max(1, n_vcpus - COMMIT_WAIT_RESERVED) */
uint64_t writable_lsn; /* own-term gate; 0 = no gate */
/* ha-term-safety task 2.4: the raft library's current-term-gated commit
* index — the COMMIT SOURCE (replaces the term-blind safe-commit). The
* barrier never advances committed_lsn past this ceiling, so an entry is
* committed only when BOTH a durable majority agrees AND the library's
* Figure-8 current-term commit rule has admitted it. 0 = no library commit
* index yet (nothing committed) — committed_lsn stays at 0. Monotonic
* non-decreasing (the library only fires on_commit_advanced on a strict
* advance), set under ce->lock via commit_set_raft_commit_index. */
/* ha-term-safety commit-ceiling-optA: the raft library's commit_index,
* recorded for OBSERVABILITY / TRACE / conformance-oracle ONLY. It does NOT
* gate commit-advance: majority_candidate_locked no longer folds it into the
* commit candidate. Commit-advance is authoritative via the durable-majority
* barrier + the own-term writable_lsn gate. Kept (and still fed by
* on_commit_advanced) so the library half keeps a sink and tooling can
* compare the library current-term commit point vs committed_lsn. Monotonic
* non-decreasing, set under ce->lock via commit_set_raft_commit_index. */
uint64_t raft_commit_index;
uint64_t scratch[MAX_STANDBYS]; /* sorted matchIndex scratch array */
} commit_engine_t;
@ -65,11 +65,13 @@ void commit_on_ack(commit_engine_t* ce, int standby_idx, uint64_t acked_lsn);
* (API ships here; no caller in this spec — unset = no gate.) */
void commit_set_writable_lsn(commit_engine_t* ce, uint64_t noop_lsn);
/* ha-term-safety task 2.4: feed the raft library's current-term-gated commit
* index as the commit SOURCE. Records the ceiling (monotonic max() — never
* regresses) and re-evaluates the barrier under the new ceiling, advancing
* committed_lsn and waking satisfied waiters. Called from the on_commit_advanced
* callback after the library admits a strict commit advance. Never blocks. */
/* ha-term-safety commit-ceiling-optA: record the raft library's commit_index
* for OBSERVABILITY / TRACE / conformance-oracle ONLY (monotonic max() — never
* regresses). This does NOT gate commit-advance: the durable-majority barrier +
* own-term writable gate are authoritative. Still fed by on_commit_advanced so
* the library half keeps a sink. Re-evaluates the (commit_index-independent)
* durable majority and wakes waiters as a convenient ACK-path wake point. Never
* blocks. */
void commit_set_raft_commit_index(commit_engine_t* ce, uint64_t commit_index);
/* Quorum-membership change (standby add/remove/catch-up transition):