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
|
|
@ -345,17 +345,36 @@ static void* fsync_worker(void* arg)
|
|||
* landed during the flush, the level is still pending and we flush
|
||||
* again without needing its signal (lost-wakeup-safe). */
|
||||
pthread_mutex_unlock(&p->fsync_mutex);
|
||||
flusher_flush_once(p);
|
||||
uint64_t before = atomic_load_explicit(&p->durable_lsn,
|
||||
memory_order_acquire);
|
||||
uint64_t after = flusher_flush_once(p);
|
||||
|
||||
/* COMMIT-EVAL WAKE (ordering fixed here; consumer wired by task 2.2):
|
||||
* durable_lsn is advanced INSIDE flusher_flush_once (atomic store with
|
||||
* release) BEFORE we return here. A WAIT caller's commit evaluation
|
||||
* reads durable_lsn via an acquire load, so once it is signalled it
|
||||
* observes the advanced watermark. Task 2.2 adds the explicit
|
||||
* commit-eval condvar broadcast at THIS point (after the durable_lsn
|
||||
* advance) — the "advance THEN wake" ordering this loop establishes is
|
||||
* what makes that wake lost-wakeup-safe. Nothing consumes durable_lsn
|
||||
* for commit yet, so no wake is needed in 2.1. */
|
||||
/* COMMIT-EVAL WAKE (task 2.2 consumer wired): durable_lsn is advanced
|
||||
* INSIDE flusher_flush_once (atomic store with release) BEFORE we
|
||||
* return here, and flusher_flush_once has already RELEASED p->lock.
|
||||
* Now — holding NO persist lock — invoke the registered durable-advance
|
||||
* callback so the commit engine re-evaluates its majority and wakes any
|
||||
* COMMIT_MODE_WAIT caller whose entry just became durable locally.
|
||||
*
|
||||
* LOST-WAKEUP SAFETY: the wake fires strictly AFTER the durable_lsn
|
||||
* release-store, and the commit engine's predicate re-reads durable_lsn
|
||||
* via an acquire load under ce->lock — so a WAIT caller either (a) sees
|
||||
* the advanced watermark on its own re-check, or (b) is blocked in
|
||||
* pthread_cond_wait and is woken by this broadcast; it can never both
|
||||
* miss the watermark AND miss the wake. We only fire the callback when
|
||||
* durable_lsn actually advanced (after > before): a no-op flush (or a
|
||||
* failed fdatasync that held the watermark) needs no commit re-eval,
|
||||
* avoiding spurious wakes.
|
||||
*
|
||||
* LOCK ORDERING (cycle-free): the flusher releases p->lock inside
|
||||
* flusher_flush_once, THEN the callback takes ce->lock (commit side).
|
||||
* The commit side only ever takes ce->lock and reads persist atomics —
|
||||
* it never takes p->lock while holding ce->lock. So the only ordering
|
||||
* is p->lock → ce->lock; there is no ce->lock → p->lock edge, hence no
|
||||
* deadlock cycle. The callback explicitly MUST NOT take p->lock. */
|
||||
if (after > before && p->on_durable_advance != NULL) {
|
||||
p->on_durable_advance(p->durable_cb_ctx);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&p->fsync_mutex);
|
||||
}
|
||||
|
|
@ -415,6 +434,12 @@ int persist_init(persist_t* p, persist_config_t* cfg, cluster_t* c)
|
|||
* flusher thread after a successful fdatasync (ha-commit-durability 2.1). */
|
||||
atomic_store(&p->durable_lsn, 0);
|
||||
|
||||
/* Durable-advance wake hook starts unregistered (ha-commit-durability 2.2):
|
||||
* the commit engine registers it in commit_engine_init. Until then every
|
||||
* flusher durable advance is a no-op wake. */
|
||||
p->on_durable_advance = NULL;
|
||||
p->durable_cb_ctx = NULL;
|
||||
|
||||
/* Create WAL and snapshot directories */
|
||||
if (mkdir_p(p->wal_path, 0700) != 0) return -errno;
|
||||
if (mkdir_p(p->snap_path, 0700) != 0) return -errno;
|
||||
|
|
@ -755,6 +780,17 @@ uint64_t persist_durable_lsn(persist_t* p)
|
|||
return atomic_load_explicit(&p->durable_lsn, memory_order_acquire);
|
||||
}
|
||||
|
||||
void persist_set_durable_cb(persist_t* p, void (*fn)(void* ctx), void* ctx)
|
||||
{
|
||||
if (!p)
|
||||
return;
|
||||
/* Set ctx before fn so the flusher (which checks fn != NULL) never sees a
|
||||
* live callback with a stale/uninitialised ctx. Registration happens in
|
||||
* commit_engine_init, before the engine is reachable by any ACK path. */
|
||||
p->durable_cb_ctx = ctx;
|
||||
p->on_durable_advance = fn;
|
||||
}
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
void persist_test_set_force_fsync_fail(persist_t* p, int fail)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue