forked from lei.chen/myxce
feat(ha-commit-durability): cap commit self-contribution at durable_lsn + durable-advance commit wake (task 2.2)
This commit is contained in:
parent
cc83caf5a0
commit
c41edc515e
10 changed files with 395 additions and 13 deletions
|
|
@ -80,11 +80,23 @@ static uint64_t majority_candidate_locked(commit_engine_t* ce)
|
|||
{
|
||||
replicator_t* r;
|
||||
uint64_t cand;
|
||||
uint64_t cap = highest_appended_lsn(ce->p);
|
||||
/* DURABILITY PRECONDITION (ha-commit-durability Req 1.2): the PRIMARY's
|
||||
* self-contribution to the commit majority is capped at its DURABLE
|
||||
* watermark (the highest LSN fdatasync'd locally), NOT the appended tip.
|
||||
* An entry that is only in the page cache (appended but not yet synced)
|
||||
* must not count self toward its majority — otherwise "committed" could be
|
||||
* durable on a single member and lost across failover (bug-661). The
|
||||
* standby side already persists-before-ACK (persist_wal_write_replicated),
|
||||
* so once self is durable-capped, committed_lsn ⇒ durable on a majority.
|
||||
* highest_appended_lsn is still used below as the never-beyond-the-log
|
||||
* bound (it can only exceed durable_lsn, so it never loosens this cap). */
|
||||
uint64_t cap = persist_durable_lsn(ce->p);
|
||||
int n = 0;
|
||||
int need, k, i;
|
||||
|
||||
/* Standalone / single-member: self constitutes the majority (Req 6.3). */
|
||||
/* Standalone / single-member: self constitutes the majority (Req 6.3) —
|
||||
* but self still only counts up to what is durable locally, so a
|
||||
* single-member commit also cannot advance over a not-yet-synced entry. */
|
||||
if (ce->n_peers <= 1) {
|
||||
return cap;
|
||||
}
|
||||
|
|
@ -184,6 +196,37 @@ static void wait_deadline(struct timespec* dl, uint32_t timeout_ms)
|
|||
}
|
||||
}
|
||||
|
||||
/* Durable-advance wake (ha-commit-durability task 2.2): invoked by the persist
|
||||
* group-commit flusher AFTER it advances durable_lsn and AFTER it releases
|
||||
* p->lock. Now that the commit majority caps self at durable_lsn, an advance of
|
||||
* durable_lsn can newly satisfy a pending entry's majority even when no standby
|
||||
* ACK arrived — re-evaluate and wake blocked COMMIT_MODE_WAIT callers so they
|
||||
* complete promptly instead of stalling until their timeout (the lost-wakeup
|
||||
* window: all standby ACKs landed before the local fdatasync finished, so
|
||||
* commit_on_ack already fired and nothing else would re-check).
|
||||
*
|
||||
* LOCK ORDERING: this runs on the flusher thread with NO persist lock held; it
|
||||
* takes only ce->lock. The commit side never takes p->lock while holding
|
||||
* ce->lock, so the only edge is p->lock → ce->lock (cycle-free, deadlock-free).
|
||||
* ctx is the commit_engine_t registered in commit_engine_init. */
|
||||
static void commit_on_durable_advance(void* ctx)
|
||||
{
|
||||
commit_engine_t* ce = ctx;
|
||||
|
||||
if (ce == NULL || ce->p == NULL) {
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&ce->lock);
|
||||
commit_advance_locked(ce); /* re-derives majority with the new durable cap;
|
||||
* broadcasts ce->cond on any advance */
|
||||
/* Broadcast even when committed_lsn did not advance: the durable watermark
|
||||
* moved, so a waiter's wait_done predicate (which the durable cap feeds via
|
||||
* commit_advance_locked) must be re-checked. commit_advance_locked only
|
||||
* broadcasts on a committed_lsn change, so cover the no-change case here. */
|
||||
pthread_cond_broadcast(&ce->cond);
|
||||
pthread_mutex_unlock(&ce->lock);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Lifecycle
|
||||
* ============================================================ */
|
||||
|
|
@ -201,6 +244,12 @@ int commit_engine_init(commit_engine_t* ce, persist_t* p, int n_peers, int n_vcp
|
|||
ce->inflight_waits = 0;
|
||||
ce->writable_lsn = 0; /* no own-term gate until set by promotion */
|
||||
memset(ce->scratch, 0, sizeof(ce->scratch));
|
||||
|
||||
/* 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
|
||||
* the reverse) dependency direction. */
|
||||
persist_set_durable_cb(p, commit_on_durable_advance, ce);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -209,6 +258,14 @@ void commit_engine_fini(commit_engine_t* ce)
|
|||
if (ce == NULL) {
|
||||
return;
|
||||
}
|
||||
/* Unregister the durable-advance wake BEFORE tearing down the mutex/cond:
|
||||
* otherwise the flusher could fire commit_on_durable_advance against a
|
||||
* destroyed engine (use-after-free / ASAN). Callers are expected to have
|
||||
* stopped the flusher (persist_stop) first, but unhooking is defensive and
|
||||
* cheap. After this, persist holds no pointer to ce. */
|
||||
if (ce->p != NULL) {
|
||||
persist_set_durable_cb(ce->p, NULL, NULL);
|
||||
}
|
||||
pthread_cond_destroy(&ce->cond);
|
||||
pthread_mutex_destroy(&ce->lock);
|
||||
ce->p = NULL;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue