forked from lei.chen/myxce
feat(ha-commit-barrier-rollout): config barrier with production ACK ingestion (task 5.2 + r1)
configAck branch: per-push majority (acked_config_lsn >= push_tag, 1+count > n_peers/2 over r->ce->n_peers, n_peers<=1 immediate, ce==NULL -EINVAL); legacy >0 counting and n_active==0 shortcut dead. push_config_to_standbys returns the barrier result; upgrade failure -> send_upgrade_error (rc forwarded), no success ACK, no restart, committed_config_tag success-only. Remediation r1 (two CRITICAL review findings): - F1: wire production config-ACK ingestion — replconfigpushreplymsg decode branch in repl_ack_decode_and_dispatch -> repl_on_config_ack on the promoted-vote ep; + defensive cluster.c OPCode_reply case. Closes the unlanded half of ha-config-bootstrap 8.1; without this the barrier would brick every multi-member config upgrade. No schema change. - F2: keep the barrier off the iomgr thread — export push_config_transport_only (non-blocking) for the 5 ha.c + join.c streaming sites; barrier path now vtask-only (do_upgrade_config). Wire-path test drives real capnp ACK bytes through the production decode branch. 30/30 rollout, 6/6 repl_majority, 46/46 config_repl; census 0 FAIL; pre-existing fixture leak only (less than baseline).
This commit is contained in:
parent
9cdeba5816
commit
5a2fae1d37
12 changed files with 1034 additions and 399 deletions
|
|
@ -407,11 +407,16 @@ void repl_on_config_ack(replicator_t *r, repl_ep_t ep, uint64_t acked_config_lsn
|
|||
* 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.
|
||||
* configAck: per-push arithmetic mirroring the engine's rules on the
|
||||
* config track (barrier-rollout task 5.2, design §Config
|
||||
* Barrier): entry_lsn carries the in-flight config push tag;
|
||||
* count = active (non-catch-up) standbys with
|
||||
* acked_config_lsn >= push_tag (a previous upgrade's ack no
|
||||
* longer counts); majority = 1 (self) + count > n_peers / 2
|
||||
* over configured members (engine n_peers, set-once at
|
||||
* wiring); n_peers <= 1 → immediate success. Returns 0 on
|
||||
* quorum, -ETIMEDOUT on timeout, -EINVAL when the engine is
|
||||
* not wired (fail-loud, mirroring the walAck branch).
|
||||
* ============================================================ */
|
||||
|
||||
int repl_wait_majority_ack(replicator_t *r, uint64_t entry_lsn,
|
||||
|
|
@ -441,7 +446,33 @@ int repl_wait_majority_ack(replicator_t *r, uint64_t entry_lsn,
|
|||
return commit_submit(r->ce, entry_lsn, COMMIT_MODE_WAIT, timeout_ms);
|
||||
}
|
||||
|
||||
/* ── configAck track (legacy standby counting; task 5 hardens it) ── */
|
||||
/* ── configAck track (per-push arithmetic; barrier-rollout task 5.2) ──
|
||||
* entry_lsn carries the in-flight config push tag (repl.h: the branch
|
||||
* reuses the walAck adapter parameter — no second parameter). */
|
||||
|
||||
uint64_t push_tag = entry_lsn;
|
||||
int n_peers;
|
||||
|
||||
if (r->ce == NULL) {
|
||||
/* The engine is wired at cluster_init_phase2 before any config
|
||||
* push reaches this wait; an unwired engine is a harness defect,
|
||||
* not a quorum — fail loud rather than fabricate a result
|
||||
* (mirrors the walAck branch's commit_submit -EINVAL). */
|
||||
WARN("repl_wait_majority_ack: configAck wait without a wired"
|
||||
" commit engine (push_tag=%016" PRIx64 ")", push_tag);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* ce and ce->n_peers are set-once before replication traffic
|
||||
* (repl_set_commit_engine contract) — safe to read without locks. */
|
||||
n_peers = r->ce->n_peers;
|
||||
|
||||
if (n_peers <= 1) {
|
||||
/* Single-member cluster: the local save is the quorum. */
|
||||
TRACE("repl_wait_majority_ack: configAck self-quorum"
|
||||
" (n_peers=%d, push_tag=%016" PRIx64 ")", n_peers, push_tag);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct timespec deadline;
|
||||
clock_gettime(CLOCK_REALTIME, &deadline);
|
||||
|
|
@ -458,8 +489,7 @@ int repl_wait_majority_ack(replicator_t *r, uint64_t entry_lsn,
|
|||
while (1) {
|
||||
mylock_lock(&r->lock);
|
||||
|
||||
int n_active = 0;
|
||||
int acked_count = 0;
|
||||
int covered = 0;
|
||||
|
||||
for (int i = 0; i < MAX_STANDBYS; i++) {
|
||||
standby_conn_t *s = &r->standbys[i];
|
||||
|
|
@ -467,26 +497,18 @@ int repl_wait_majority_ack(replicator_t *r, uint64_t entry_lsn,
|
|||
continue;
|
||||
if (atomic_load_explicit(&s->in_catch_up, memory_order_acquire))
|
||||
continue;
|
||||
n_active++;
|
||||
if (atomic_load_explicit(&s->acked_config_lsn,
|
||||
memory_order_acquire) > 0)
|
||||
acked_count++;
|
||||
memory_order_acquire) >= push_tag)
|
||||
covered++;
|
||||
}
|
||||
|
||||
mylock_unlock(&r->lock);
|
||||
|
||||
/* 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 = acked_count; /* no self-count for configAck */
|
||||
|
||||
if (acked_total >= quorum) {
|
||||
/* Majority over configured members, self counted: the primary's
|
||||
* own save plus standbys covering the in-flight push tag. No
|
||||
* n_active shortcut — a multi-member cluster with zero covering
|
||||
* acks must time out, never fabricate success (Req 4.2). */
|
||||
if (1 + covered > n_peers / 2) {
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue