feat(raft-apply-decoupling): commit engine resolve primitive + majority-ack wiring (task 1.2)

commit_resolve() delivers a registered continuation's outcome exactly
once (idempotent re-resolve), dispatched off-thread via
vtask_enq_classified(VTASK_LIVE_BLOCK) so it never runs inline on the
replication-ACK or iomgr path. Wired into all 6 commit.c-internal
paths that can advance committed_lsn/writable_lsn.

Review caught a real task-graph gap, not a defect: converting
COMMIT_MODE_WAIT away from a blocking wait also removes the only
mechanism that ever detected a commit timing out (the caller's own
cond_timedwait deadline). Nothing yet arms an equivalent timeout for
async-registered continuations, and nothing wires demotion/log-truncation
detection to commit_resolve(FAILED, ...) -- left as-is, a pending async
command during quorum loss would hang forever instead of failing,
which is worse than today. Split into new tasks 1.3 (timeout arming)
and 1.4 (demotion/truncation wiring), renumbered the old concurrency
test to 1.5, and made 3.2 depend on both since its failure/timeout
observable is meaningless without them. design.md's File Structure
Plan updated to reflect that ha.c/persist.c need a narrow touch for
1.4's detection hooks.
This commit is contained in:
Rongsong Shen 2026-07-03 09:03:50 +00:00
parent 10d157f8c5
commit fd7a0746eb
5 changed files with 558 additions and 19 deletions

View file

@ -127,6 +127,28 @@ int commit_submit(commit_engine_t* ce, uint64_t lsn, commit_mode_t mode,
int commit_submit_async(commit_engine_t* ce, uint64_t lsn,
commit_continuation_fn fn, void* ctx);
/* raft-apply-decoupling task 1.2: resolve lsn's registered continuation (if
* any) with the given outcome/rc, exactly once. Called internally on the
* majority-commit path (commit_advance_locked and its callers, via the
* wait_done_locked predicate -- mirrors COMMIT_MODE_WAIT's own completion
* check) and is also the PUBLIC failure-path hook: a future caller detecting
* timeout, demotion, or log-tail truncation for a pending lsn calls this
* directly with COMMIT_OUTCOME_FAILED and the appropriate rc (the existing
* error-code vocabulary -- e.g. -ETIMEDOUT, -XCE_ERR_STANDBY). Idempotent: a
* second resolve() for an lsn with no registered (or already-resolved)
* continuation is a silent no-op -- lsn == 0 is the free-slot sentinel, so a
* cleared slot never matches again. Never invokes the continuation inline on
* the calling thread: dispatch happens off-thread via vtask_enq_classified
* (VTASK_LIVE_BLOCK -- never admission-gated, so delivery cannot fast-reject
* the way VTASK_REQ_BLOCK could at the pool's blocking cap), falling back to
* an inline call ONLY if dispatch is unavailable (OOM, or no vtaskmgr pool
* wired -- commit-only unit fixtures), because a continuation must never be
* silently left unresolved (Req 9.4; "controllers never self-terminate" —
* fail loud and complete the work rather than drop it). Safe to call from any
* thread that does NOT already hold ce->lock. */
void commit_resolve(commit_engine_t* ce, uint64_t lsn, commit_outcome_t outcome,
int rc);
/* iomgr path: record standby ACK, evaluate majority, advance committed_lsn
* contiguously, wake satisfied waiters. Never blocks. */
void commit_on_ack(commit_engine_t* ce, int standby_idx, uint64_t acked_lsn);