feat(ha-commit-engine): engine instantiation + sync-append path switch (task 5.3)
cluster_t embeds commit_engine_t; init in cluster_init_phase2 after the vtaskmgr guard (idempotence-guarded — phase2 can run twice), wiring repl_set_commit_engine + ha.repl back-pointer before any standby can register; fini after iomgr/vtask shutdown. persist_wal_sync_append passes written_lsn; engine-wired path drops -ENODEV and the committed_lsn store (engine owns advancement); LSN-0 first-entry routes to legacy per the accepted interim. p2 -ENODEV pin updated to the Req 6.3 contract. 34/34 + 6/6 green through honest delegation; census exactly at the 3 validated baselines; production binary links.
This commit is contained in:
parent
6600fd5d1f
commit
f0d6074d4e
6 changed files with 142 additions and 50 deletions
|
|
@ -4,6 +4,7 @@
|
|||
#include "ctrl_metrics.h"
|
||||
#include "hook.h"
|
||||
#include "xce/cluster_def.h"
|
||||
#include "xce/errnox.h"
|
||||
#include "xce/types.h"
|
||||
#include "controller.capnp.h"
|
||||
#include "xlist.h"
|
||||
|
|
@ -1011,44 +1012,71 @@ int persist_wal_sync_append(persist_t* p, uint32_t type,
|
|||
return rc;
|
||||
}
|
||||
|
||||
/* Majority-commit replication (Req 5.1–5.3).
|
||||
* After local fsync via persist_wal_async, wait for majority quorum ACK.
|
||||
* On quorum: advance committed_lsn. On no standbys or timeout: degraded.
|
||||
/* Majority-commit replication (Req 5.1–5.3; ha-commit-engine Req 1.1–1.3).
|
||||
* After local fsync via persist_wal_async, confirm per-entry majority.
|
||||
* We use current_lsn - 1 because persist_wal_async already incremented it. */
|
||||
uint64_t written_lsn;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
written_lsn = p->current_lsn - 1;
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
/* No standbys registered → degraded mode immediately (no quorum to wait for) */
|
||||
mylock_lock(&p->repl.lock);
|
||||
int has_standbys = (p->repl.nstandbys > 0);
|
||||
mylock_unlock(&p->repl.lock);
|
||||
|
||||
int sync_err;
|
||||
if (!has_standbys) {
|
||||
sync_err = -ENODEV;
|
||||
} else {
|
||||
/* entry_lsn = 0: mechanical signature update only (ha-commit-engine
|
||||
* task 5.1) — the legacy body ignores it. Task 5.3 passes
|
||||
* written_lsn so the wired engine evaluates per-entry majority. */
|
||||
sync_err = repl_wait_majority_ack(&p->repl, 0, p->config.sync_timeout_ms,
|
||||
/* Engine-wired path (ha-commit-engine task 5.3): delegate the commit
|
||||
* decision to the commit engine through the walAck adapter. The engine
|
||||
* is the single committed_lsn writer (no store here), and its
|
||||
* n_peers<=1 self-quorum replaces the old has_standbys → -ENODEV
|
||||
* degraded special case (Req 6.3).
|
||||
*
|
||||
* LSN-0 edge (ACCEPTED interim behavior — spec Implementation Notes):
|
||||
* written_lsn is passed unconditionally, but the FIRST-EVER WAL entry
|
||||
* of a fresh cluster is LSN 0 and commit_submit rejects lsn==0, so the
|
||||
* adapter's entry_lsn != 0 guard routes that one entry to the legacy
|
||||
* branch below. This happens once per cluster lifetime;
|
||||
* ha-single-leader-role's promotion no-op entry eliminates it in
|
||||
* practice. */
|
||||
if (p->repl.ce != NULL && written_lsn != 0) {
|
||||
sync_err = repl_wait_majority_ack(&p->repl, written_lsn,
|
||||
p->config.sync_timeout_ms,
|
||||
(uint8_t)OPCode_walAck);
|
||||
if (sync_err == 0)
|
||||
atomic_store_explicit(&p->committed_lsn, written_lsn, memory_order_release);
|
||||
else
|
||||
if (sync_err != 0)
|
||||
CTRL_METRIC_INC(ctrl_repl_degraded_count);
|
||||
} else {
|
||||
/* DEPRECATED legacy branch: engine-less (ce == NULL) unit-test
|
||||
* contexts plus the LSN-0 first entry. Connectivity-based counting;
|
||||
* committed_lsn is stored here only because no engine owns it.
|
||||
* Retired by ha-commit-barrier-rollout. */
|
||||
mylock_lock(&p->repl.lock);
|
||||
int has_standbys = (p->repl.nstandbys > 0);
|
||||
mylock_unlock(&p->repl.lock);
|
||||
|
||||
if (!has_standbys) {
|
||||
/* No standbys registered → degraded mode (no quorum to wait for) */
|
||||
sync_err = -ENODEV;
|
||||
} else {
|
||||
sync_err = repl_wait_majority_ack(&p->repl, written_lsn,
|
||||
p->config.sync_timeout_ms,
|
||||
(uint8_t)OPCode_walAck);
|
||||
if (sync_err == 0)
|
||||
atomic_store_explicit(&p->committed_lsn, written_lsn,
|
||||
memory_order_release);
|
||||
else
|
||||
CTRL_METRIC_INC(ctrl_repl_degraded_count);
|
||||
}
|
||||
}
|
||||
if (sync_rc_out)
|
||||
*sync_rc_out = sync_err;
|
||||
/* sync_err < 0 — replication failed.
|
||||
* -ENODEV: no standbys registered (single-node or degraded).
|
||||
* -ETIMEDOUT: standbys were registered but didn't ACK in time.
|
||||
* Only -ENODEV is acceptable for forward progress; timeout means
|
||||
* the cluster had standbys but couldn't reach quorum — the write
|
||||
* must be rejected to preserve the majority-consistency invariant. */
|
||||
if (sync_err == -ETIMEDOUT)
|
||||
return -ETIMEDOUT;
|
||||
* -ENODEV: legacy degraded mode (no standbys, engine-less) — acceptable
|
||||
* for forward progress.
|
||||
* -ETIMEDOUT: no per-entry majority within the deadline — the write
|
||||
* must be rejected to preserve the majority-consistency
|
||||
* invariant.
|
||||
* -XCE_ERR_NOT_READY: engine admission cap exhausted — retriable
|
||||
* rejection.
|
||||
* Timeout and admission rejection propagate to the job-submit error
|
||||
* path (ha-commit-engine Req 1.1, 4.4). */
|
||||
if (sync_err == -ETIMEDOUT || sync_err == -XCE_ERR_NOT_READY)
|
||||
return sync_err;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue