feat(raft-apply-decoupling): add commit engine continuation registration (task 1.1)
commit_submit_async() lets a WAIT-mode caller register a continuation against an appended LSN instead of blocking on pthread_cond_timedwait, reusing the existing admission-cap discipline (wait_admit_locked/ wait_release_locked) so the bounded-capacity guarantee carries over unchanged. Purely additive: no existing call site (repl_wait_majority_ack) is touched yet -- that switch lands in task 3.2 once commit_resolve() (task 1.2) exists to drive it.
This commit is contained in:
parent
3ecb724984
commit
10d157f8c5
6 changed files with 457 additions and 1 deletions
|
|
@ -30,6 +30,7 @@
|
|||
*/
|
||||
#include <errno.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
|
|
@ -311,6 +312,20 @@ int commit_engine_init(commit_engine_t* ce, persist_t* p, int n_peers, int n_vcp
|
|||
ce->raft_commit_index = 0; /* advisory record only; does not gate commit (optA) */
|
||||
memset(ce->scratch, 0, sizeof(ce->scratch));
|
||||
|
||||
/* raft-apply-decoupling task 1.1: continuation registry, sized to
|
||||
* n_vcpus (see the field comment in commit.h for the sizing invariant:
|
||||
* wait_cap(ce) <= n_vcpus always, so n_vcpus slots suffice regardless
|
||||
* of the blocking-WAIT/async-registration mix sharing inflight_waits).
|
||||
* calloc zero-fills every slot's lsn to 0, the free-slot sentinel. */
|
||||
ce->continuations = (commit_continuation_t*)calloc(
|
||||
(size_t)n_vcpus, sizeof(commit_continuation_t));
|
||||
if (ce->continuations == NULL) {
|
||||
pthread_cond_destroy(&ce->cond);
|
||||
pthread_mutex_destroy(&ce->lock);
|
||||
return -ENOMEM;
|
||||
}
|
||||
ce->n_continuations = n_vcpus;
|
||||
|
||||
/* Register the durable-advance wake so the flusher can re-trigger commit
|
||||
* evaluation when durable_lsn moves (task 2.2). Persist owns the hook slot;
|
||||
* we only register a function + ctx, preserving the commit→persist (never
|
||||
|
|
@ -346,6 +361,9 @@ void commit_engine_fini(commit_engine_t* ce)
|
|||
if (ce->p != NULL) {
|
||||
persist_set_durable_cb(ce->p, NULL, NULL);
|
||||
}
|
||||
free(ce->continuations);
|
||||
ce->continuations = NULL;
|
||||
ce->n_continuations = 0;
|
||||
pthread_cond_destroy(&ce->cond);
|
||||
pthread_mutex_destroy(&ce->lock);
|
||||
ce->p = NULL;
|
||||
|
|
@ -433,6 +451,74 @@ int commit_submit(commit_engine_t* ce, uint64_t lsn, commit_mode_t mode,
|
|||
return rc;
|
||||
}
|
||||
|
||||
/* Caller holds ce->lock. Returns the index of a free (lsn == 0) slot, or -1
|
||||
* if none is free. n_continuations == n_vcpus >= wait_cap(ce) >=
|
||||
* inflight_waits at all times (the same admission cap governs both this
|
||||
* registry and COMMIT_MODE_WAIT's blocking parks, sharing one budget), so an
|
||||
* admitted registration always finds a free slot -- see the commit.h field
|
||||
* comment for the sizing invariant. */
|
||||
static int continuation_find_free_slot_locked(const commit_engine_t* ce)
|
||||
{
|
||||
for (int i = 0; i < ce->n_continuations; i++) {
|
||||
if (ce->continuations[i].lsn == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* raft-apply-decoupling task 1.1: event-driven registration, the
|
||||
* non-blocking sibling of commit_submit(..., COMMIT_MODE_WAIT, ...).
|
||||
* Registers `fn`/`ctx` against `lsn` and returns immediately -- it never
|
||||
* evaluates or waits on the commit predicate itself. Resolution (invoking
|
||||
* `fn` on majority-ack / timeout / demotion / truncation) is
|
||||
* commit_resolve's job, wired onto commit_advance_locked and the existing
|
||||
* failure paths by a follow-up task; until then a registered slot is
|
||||
* inert. */
|
||||
int commit_submit_async(commit_engine_t* ce, uint64_t lsn,
|
||||
commit_continuation_fn fn, void* ctx)
|
||||
{
|
||||
int slot, arc;
|
||||
|
||||
if (ce == NULL || ce->p == NULL || lsn == 0 || fn == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ce->lock);
|
||||
|
||||
/* Same admission-cap discipline as COMMIT_MODE_WAIT (Req 9.1, 9.2): a
|
||||
* registered continuation occupies the identical bounded slot a
|
||||
* blocking wait would have parked in, so the two calling conventions
|
||||
* share one budget while both exist side by side. */
|
||||
arc = wait_admit_locked(ce);
|
||||
if (arc != 0) {
|
||||
pthread_mutex_unlock(&ce->lock);
|
||||
WARN("commit: async registration rejected, admission cap exhausted "
|
||||
"(pool=%s n_vcpus=%d lsn=%lu)",
|
||||
ce->pool ? "pool" : "local", ce->n_vcpus, lsn);
|
||||
return arc;
|
||||
}
|
||||
|
||||
slot = continuation_find_free_slot_locked(ce);
|
||||
if (slot < 0) {
|
||||
/* Unreachable given the sizing invariant above; fail loud rather
|
||||
* than silently drop the registration if it is ever violated. */
|
||||
wait_release_locked(ce);
|
||||
pthread_mutex_unlock(&ce->lock);
|
||||
WARN("commit: async registration rejected, no free continuation "
|
||||
"slot (lsn=%lu, n_continuations=%d) -- sizing invariant "
|
||||
"violated", lsn, ce->n_continuations);
|
||||
return -XCE_ERR_NOT_READY;
|
||||
}
|
||||
|
||||
ce->continuations[slot].lsn = lsn;
|
||||
ce->continuations[slot].fn = fn;
|
||||
ce->continuations[slot].ctx = ctx;
|
||||
|
||||
pthread_mutex_unlock(&ce->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* ACK and membership events
|
||||
* ============================================================ */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue