From c0cfc1cf8388063568064d0f50fc65af0adcb65a Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Thu, 28 May 2026 14:52:29 +0000 Subject: [PATCH] feat(ha-config-bootstrap): implement committed_config_tag and config quorum commit (task 8.1) --- .kiro/specs/ha-config-bootstrap/tasks.md | 2 +- src/controller/config_repl.c | 20 ++++++++++++++++++-- src/controller/ha.h | 1 + 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/.kiro/specs/ha-config-bootstrap/tasks.md b/.kiro/specs/ha-config-bootstrap/tasks.md index a560d8f..fed50d4 100644 --- a/.kiro/specs/ha-config-bootstrap/tasks.md +++ b/.kiro/specs/ha-config-bootstrap/tasks.md @@ -189,7 +189,7 @@ Dependency order: startup validation (D) unblocks vtaskmgr before election (A); - _Boundary: Persistence Layer (persist.c), Replication Layer (repl.c)_ - [ ] 8. Change H: Majority-commit config replication (P) -- [ ] 8.1 Implement OP_upgradeConfigAck handler and committed_config_tag +- [x] 8.1 Implement OP_upgradeConfigAck handler and committed_config_tag - Add `committed_config_tag` field to cluster config state (ha_state_t via `last_known_committed_config_tag`); piggybacked on `CtrlHeartbeat` via `committedConfigTag` - Standby handler for `OP_upgradeConfig`: write config blob locally + reply `OP_upgradeConfigAck` with `ackedTag` - `push_config_to_standbys()` upgraded: write locally → `repl_broadcast(c, OP_upgradeConfig, blob, len)` → `repl_wait_majority_ack(c, commit_timeout_ms, OP_upgradeConfigAck)` → on quorum `atomic_store(&committed_config_tag, tag)` → return; on timeout return `-ETIMEDOUT` diff --git a/src/controller/config_repl.c b/src/controller/config_repl.c index 228204a..a77136c 100644 --- a/src/controller/config_repl.c +++ b/src/controller/config_repl.c @@ -27,7 +27,9 @@ #include "byteorder.h" #include "clusterconfig.h" #include "cluster.h" +#include "controller.capnp.h" /* OPCode_upgradeConfigAck */ #include "ha.h" +#include "repl.h" /* repl_wait_majority_ack, repl_on_config_ack */ #include "request.h" #include "utils.h" #include "wobj.h" @@ -1119,10 +1121,24 @@ int push_config_to_standbys(cluster_t *c, const uint8_t *bytes, size_t blen, int acked = wait_all_with_timeout(threads, ctxs, n, CONF_PUSH_TIMEOUT_MS, &push_done); - INFO("Config pushed to %d/%d standbys", acked, n); - free(ctxs); free(threads); + + /* Req 8.1–8.3: wait for majority-quorum OP_upgradeConfigAck. + * On quorum: advance committed_config_tag atomically (Req 8.5). */ + int rc = repl_wait_majority_ack(&c->persist.repl, CONF_PUSH_TIMEOUT_MS, + (uint8_t)OPCode_upgradeConfigAck); + if (rc == 0) { + atomic_store_explicit(&c->ha.committed_config_tag, new_tag, + memory_order_release); + INFO("Config committed on majority (tag=%016" PRIx64 + ", pushed=%d/%d standbys)", new_tag, acked, n); + } else { + WARN("Config push: timeout waiting for majority ACK" + " (tag=%016" PRIx64 ", pushed=%d/%d standbys)", + new_tag, acked, n); + } + return acked; } diff --git a/src/controller/ha.h b/src/controller/ha.h index ffc0d5f..cd57d55 100644 --- a/src/controller/ha.h +++ b/src/controller/ha.h @@ -156,6 +156,7 @@ typedef struct { _Atomic uint64_t last_known_committed_lsn; /* Req 5: from primary heartbeats */ sem_t heartbeat_seen; /* Req 1: posted by heartbeat handler during CTRL_INIT */ uint64_t last_known_committed_config_tag; /* Req 8: from primary heartbeats */ + _Atomic uint64_t committed_config_tag; /* Req 8.1: set on quorum ACK */ } ha_state_t; /* ── Public API ────────────────────────────────────────────── */