feat(raft-apply-decoupling): resolve pending continuations on demotion/truncation (task 1.4)
commit_resolve_pending_above(ce, lsn_floor, rc) fails every registered continuation for lsn > lsn_floor, reusing commit_resolve()'s existing exactly-once/idempotent guarantee (no parallel bookkeeping path). Truncation: a new on_truncate/truncate_cb_ctx callback on persist_t (same inversion-of-control shape as the existing on_durable_advance hook) fires from persist_truncate_wal_above after releasing p->lock, only when entries were actually removed. This one wrapper covers all three real truncation call sites: raft_cb_become_leader's post-election truncate, the library's on_truncate_from conflict-reconcile, and ha_demotion_convergence_fn's post-demotion truncate. Demotion: raft_cb_become_follower calls commit_resolve_pending_above(&c->commit, 0, -XCE_ERR_STANDBY) -- lsn_floor=0 fails everything, since a demoted node can never authoritatively confirm anything still pending (CP posture). Without this, a demoted or truncated node would leave any pending async command permanently unresolved -- the requester would simply never get a reply, worse than today's bounded-timeout behavior. Also closes bug-1407 (pre-existing test_config_repl flake, unrelated to this feature) -- confirmed fixed as a side effect of prior tasks in this series, 70/70 passing across 3 independent runs.
This commit is contained in:
parent
530a02bda3
commit
9b4b805a27
9 changed files with 748 additions and 301 deletions
|
|
@ -79,6 +79,12 @@ static void commit_resolve_pending(commit_engine_t* ce);
|
|||
* commit_sweep_scan_locked, the locked-scan helper it drives). */
|
||||
static void* commit_sweep_worker(void* arg);
|
||||
|
||||
/* Forward declaration: raft-apply-decoupling task 1.4. commit_engine_init
|
||||
* (below) registers this as persist's truncation-wake callback; it is
|
||||
* defined in the Continuation Resolution section further down this file
|
||||
* (alongside commit_resolve_pending_above, the helper it delegates to). */
|
||||
static void commit_on_truncate(void* ctx, uint64_t cutoff_lsn);
|
||||
|
||||
/* ============================================================
|
||||
* Internal helpers
|
||||
* ============================================================ */
|
||||
|
|
@ -387,6 +393,14 @@ int commit_engine_init(commit_engine_t* ce, persist_t* p, int n_peers, int n_vcp
|
|||
* the reverse) dependency direction. */
|
||||
persist_set_durable_cb(p, commit_on_durable_advance, ce);
|
||||
|
||||
/* raft-apply-decoupling task 1.4: register the truncation wake alongside
|
||||
* the durable-advance wake -- same dependency direction (persist stores
|
||||
* an opaque callback; commit.c is never a dependency of persist.c). Fired
|
||||
* by persist_truncate_wal_above after it actually removes entries above
|
||||
* a new tail, so any continuation registered above that tail (which can
|
||||
* never commit -- its entry no longer exists) resolves FAILED promptly. */
|
||||
persist_set_truncate_cb(p, commit_on_truncate, ce);
|
||||
|
||||
/* raft-apply-decoupling task 1.3: start the dedicated timeout-sweep
|
||||
* thread (see file header for why this is a private thread rather than
|
||||
* the shared controller delaymgr). A creation failure is treated as a
|
||||
|
|
@ -402,6 +416,7 @@ int commit_engine_init(commit_engine_t* ce, persist_t* p, int n_peers, int n_vcp
|
|||
int prc = pthread_create(&ce->sweep_thread, NULL, commit_sweep_worker, ce);
|
||||
if (prc != 0) {
|
||||
persist_set_durable_cb(p, NULL, NULL);
|
||||
persist_set_truncate_cb(p, NULL, NULL);
|
||||
free(ce->continuations);
|
||||
ce->continuations = NULL;
|
||||
ce->n_continuations = 0;
|
||||
|
|
@ -453,6 +468,7 @@ void commit_engine_fini(commit_engine_t* ce)
|
|||
* cheap. After this, persist holds no pointer to ce. */
|
||||
if (ce->p != NULL) {
|
||||
persist_set_durable_cb(ce->p, NULL, NULL);
|
||||
persist_set_truncate_cb(ce->p, NULL, NULL);
|
||||
}
|
||||
free(ce->continuations);
|
||||
ce->continuations = NULL;
|
||||
|
|
@ -801,6 +817,65 @@ static void commit_resolve_pending(commit_engine_t* ce)
|
|||
}
|
||||
}
|
||||
|
||||
/* raft-apply-decoupling task 1.4: the FAILED-outcome sibling of
|
||||
* commit_resolve_pending -- instead of scanning for majority-satisfied
|
||||
* entries, resolves every registered continuation whose lsn > lsn_floor as
|
||||
* FAILED with rc. See commit.h for the full contract and the two intended
|
||||
* callers (log-tail truncation, demotion). Caller must NOT hold ce->lock. */
|
||||
void commit_resolve_pending_above(commit_engine_t* ce, uint64_t lsn_floor,
|
||||
int rc)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
if (ce == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t failed[ce->n_continuations]; /* VLA: bounded by n_vcpus -- see
|
||||
* the commit.h field-comment
|
||||
* sizing invariant */
|
||||
|
||||
pthread_mutex_lock(&ce->lock);
|
||||
for (int i = 0; i < ce->n_continuations; i++) {
|
||||
uint64_t lsn = ce->continuations[i].lsn;
|
||||
|
||||
if (lsn != 0 && lsn > lsn_floor) {
|
||||
failed[n++] = lsn;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&ce->lock);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
commit_resolve(ce, failed[i], COMMIT_OUTCOME_FAILED, rc);
|
||||
}
|
||||
}
|
||||
|
||||
/* Truncation wake (raft-apply-decoupling task 1.4): registered with persist
|
||||
* via persist_set_truncate_cb (commit_engine_init) and invoked by
|
||||
* persist_truncate_wal_above AFTER it actually removes entries above the new
|
||||
* tail (cutoff_lsn) and AFTER it releases p->lock -- persist only fires this
|
||||
* when a truncation genuinely happened (current_lsn strictly decreased); a
|
||||
* no-op truncate never calls this. Every continuation registered above the
|
||||
* new tail can never commit (its entry no longer exists in the log) --
|
||||
* resolve it FAILED now rather than leaving it for the timeout-sweep thread
|
||||
* to eventually time out. -ENOENT mirrors the truncation-outcome vocabulary
|
||||
* already exercised in test_commit_resolve.c (task 1.2).
|
||||
*
|
||||
* LOCK ORDERING: mirrors commit_on_durable_advance -- runs with NO persist
|
||||
* lock held, takes only ce->lock (via commit_resolve_pending_above /
|
||||
* commit_resolve). Same cycle-free p->lock -> ce->lock edge; the commit side
|
||||
* never takes p->lock while holding ce->lock. ctx is the commit_engine_t
|
||||
* registered in commit_engine_init. */
|
||||
static void commit_on_truncate(void* ctx, uint64_t cutoff_lsn)
|
||||
{
|
||||
commit_engine_t* ce = ctx;
|
||||
|
||||
if (ce == NULL || ce->p == NULL) {
|
||||
return;
|
||||
}
|
||||
commit_resolve_pending_above(ce, cutoff_lsn, -ENOENT);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Timeout sweep (raft-apply-decoupling task 1.3)
|
||||
* ============================================================ */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue