feat(ha-commit-barrier-rollout): persist_commit_barrier + legacy walAck retirement (task 2.2)

Barrier: watermark = current_lsn - 1 under p->lock (released before
blocking), covered/nothing-appended/LSN-0 short-circuits, sync_timeout_ms
fallback, engine code unmodified, ce==NULL vacuous. repl.c walAck track
now engine-only (legacy acked_lsn>0 counting and walAck-reachable
n_active==0 success deleted); configAck track byte-equivalent for
task 5. 15/15 group; census 472/472.
This commit is contained in:
Rongsong Shen 2026-06-12 05:36:42 +00:00
parent 611fbc8d7b
commit 8f688eace1
5 changed files with 106 additions and 54 deletions

View file

@ -399,17 +399,19 @@ void repl_on_config_ack(replicator_t *r, repl_ep_t ep, uint64_t acked_config_lsn
}
/* ============================================================
* Majority-quorum ACK wait (task 7.3)
* Majority-quorum ACK wait (task 7.3; walAck track rebased onto the
* commit engine by ha-commit-barrier-rollout task 2.2)
*
* Quorum formula: quorum = (n_active + 2) / 2 [ceiling of (n_active+1)/2]
* n_active = count of active (non-catch-up) standbys.
*
* walAck: acked_total = 1 (self) + standbys with acked_lsn > 0.
* Primary counts itself — it persists WAL before broadcasting.
* configAck: acked_total = standbys with acked_config_lsn > 0 (no self-count).
* Config must be confirmed by standbys; primary does not self-count.
*
* Returns 0 on quorum, -ETIMEDOUT on timeout.
* walAck: per-LSN commit evaluation, delegated to the commit engine
* unconditionally (the legacy connectivity-based "ACKed
* anything ever" counting with self-count and the zero-active
* success short-circuit is retired). Returns the engine code
* unmodified: 0 / -ETIMEDOUT / -XCE_ERR_NOT_READY / -EINVAL.
* configAck: legacy standby counting (hardened by barrier-rollout task 5):
* quorum = (n_active + 2) / 2 over active (non-catch-up)
* standbys; acked_total = standbys with acked_config_lsn > 0,
* NO self-count (config must be confirmed by standbys).
* Returns 0 on quorum, -ETIMEDOUT on timeout.
* ============================================================ */
int repl_wait_majority_ack(replicator_t *r, uint64_t entry_lsn,
@ -418,27 +420,28 @@ int repl_wait_majority_ack(replicator_t *r, uint64_t entry_lsn,
if (r == NULL)
return -EINVAL;
int is_wal_ack = (ack_op == (uint8_t)OPCode_walAck);
if (ack_op == (uint8_t)OPCode_walAck) {
/* LSN-0 edge: the first-ever WAL entry of a fresh cluster is LSN 0
* (current_lsn starts at 0; written_lsn = current_lsn - 1) and is
* structurally un-commitable — commit_submit rejects lsn == 0.
* Documented once-per-cluster-lifetime interim behavior
* (ha-commit-engine Implementation Notes): confirm it immediately;
* the promotion no-op entry (ha-single-leader-role) eliminates the
* case in practice. configAck callers also pass entry_lsn = 0 but
* are dispatched by ack_op above, never through this guard. */
if (entry_lsn == 0)
return 0;
/* Engine adapter (ha-commit-engine task 5.2, design §Job-Submit Adapter):
* the walAck barrier is per-LSN commit evaluation owned by the commit
* engine when wired. Caller holds no repl locks here, so the engine's
* ce->lock -> r->lock order is preserved.
*
* entry_lsn == 0 falls through to legacy counting: persist.c's
* persist_wal_sync_append still passes 0 until task 5.3 forwards
* written_lsn (and commit_submit rejects lsn==0 with -EINVAL). Note the
* first-ever WAL entry IS LSN 0 — structurally un-commitable under the
* current numbering; task 5.3 documents the resolution, and the
* promotion no-op entry (ha-single-leader-role) eliminates it in
* practice. configAck callers pass entry_lsn=0 and never delegate. */
if (is_wal_ack && r->ce != NULL && entry_lsn != 0) {
/* Unconditional engine delegation (ha-commit-engine task 5.2,
* design §Job-Submit Adapter). Caller holds no repl locks here, so
* the engine's ce->lock -> r->lock order is preserved. ce == NULL
* (engine never wired) is unreachable in production — the engine
* is wired at cluster_init_phase2 — and commit_submit surfaces it
* as -EINVAL rather than this adapter fabricating a quorum result. */
return commit_submit(r->ce, entry_lsn, COMMIT_MODE_WAIT, timeout_ms);
}
/* DEPRECATED legacy counting below (connectivity-based, not per-LSN):
* survives only for engine-less (ce == NULL) unit-test contexts and the
* configAck branch; retired by ha-commit-barrier-rollout. */
/* ── configAck track (legacy standby counting; task 5 hardens it) ── */
struct timespec deadline;
clock_gettime(CLOCK_REALTIME, &deadline);
@ -465,27 +468,23 @@ int repl_wait_majority_ack(replicator_t *r, uint64_t entry_lsn,
if (atomic_load_explicit(&s->in_catch_up, memory_order_acquire))
continue;
n_active++;
if (is_wal_ack) {
if (atomic_load_explicit(&s->acked_lsn, memory_order_acquire) > 0)
acked_count++;
} else {
if (atomic_load_explicit(&s->acked_config_lsn,
memory_order_acquire) > 0)
acked_count++;
}
if (atomic_load_explicit(&s->acked_config_lsn,
memory_order_acquire) > 0)
acked_count++;
}
mylock_unlock(&r->lock);
/* Single-node shortcircuit: no active standbys → local write is quorum.
* Applies to both walAck (self=1, quorum=1) and configAck (Req 8.5). */
/* Single-node shortcircuit: no active standbys → local write is
* quorum (configAck only, Req 8.5 — walAck never reaches this
* loop; its single-member case is the engine's self-quorum). */
if (n_active == 0) {
rc = 0;
break;
}
int quorum = (n_active + 2) / 2;
int acked_total = is_wal_ack ? (1 + acked_count) : acked_count;
int acked_total = acked_count; /* no self-count for configAck */
if (acked_total >= quorum) {
rc = 0;