feat(controller-status): source per-controller connection status in ctrlstatus_fill (task 8.2)
Populates CtrlPeer.status (added in task 8.1) inside ctrlstatus_fill's peer-building loop: self always reports ok; a primary responder sources every other peer from the live replication connection registry (replicator_t.standbys[], host+port matched, reusing repl_active_standby_count's ep!=NULL && !in_catch_up predicate) -- dead/catching-up/ok; a non-primary responder reports unknown for every non-self peer. A self-identity mismatch (pre-existing misconfiguration) logs one WARN and falls through to the normal non-self sourcing rather than forcing any peer to ok. Read-only: a bounded lock-protected scan, no state mutation. TDD: 3 new tests (primary-sourced-from-registry, standby-unknown-except- self, self-mismatch-falls-through) plus an extended no-mutation assertion; RED->GREEN independently reproduced by the reviewer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
1b278c0e9a
commit
ab5dd91537
3 changed files with 288 additions and 1 deletions
|
|
@ -22,6 +22,7 @@
|
|||
#include "cluster.h"
|
||||
#include "ha.h"
|
||||
#include "persist.h"
|
||||
#include "utils.h" /* WARN */
|
||||
#include "xce/cluster_def.h"
|
||||
|
||||
/*
|
||||
|
|
@ -197,13 +198,89 @@ void ctrlstatus_fill(cluster_t* c, xce_ctrlstatus_t* st)
|
|||
if (n > 0) {
|
||||
st->peers.list = (xce_ctrlpeer_t**)calloc((size_t)n,
|
||||
sizeof(xce_ctrlpeer_t*));
|
||||
|
||||
/* per-controller connection status (Req 13.1-13.5; design.md Req 13
|
||||
* delta, Locked Decisions 3-6): the responder's own entry always
|
||||
* reports ok; when the responder is currently primary, every OTHER
|
||||
* peer is sourced by host+port match against the live replication
|
||||
* connection registry (replicator_t.standbys[]) -- no live
|
||||
* connection -> dead, live but not yet caught up -> catching-up,
|
||||
* live and caught up -> ok; when the responder is not primary,
|
||||
* every other peer is unknown, since only the primary has direct
|
||||
* connection visibility into every other node. */
|
||||
int is_primary = (st->responderRole == (uint8_t)CTRL_PRIMARY);
|
||||
/* ha->repl is a back-pointer wired once commit-engine setup runs
|
||||
* (cluster.c) and may be NULL before that (or in a minimal test
|
||||
* fixture); &c->persist.repl is always a valid fallback (mirrors
|
||||
* the ha->repl ? ha->repl : &p->repl idiom at ha.c:2673). */
|
||||
replicator_t* repl = ha->repl ? ha->repl : &c->persist.repl;
|
||||
|
||||
/* Self-match fallback (design-review finding): if this responder's
|
||||
* own identity matches none of its configured peers -- a pre-
|
||||
* existing misconfiguration, not new to this delta -- WARN once per
|
||||
* call and let every peer fall through to the normal non-self
|
||||
* branches below. No entry is force-corrected to ok in this case;
|
||||
* this is an explicit, logged degradation rather than a silent or
|
||||
* fabricated result. */
|
||||
int self_found = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (strcmp(mc->peers_cfg.candidates[i].name,
|
||||
st->responderName) == 0) {
|
||||
self_found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!self_found) {
|
||||
WARN("ctrlstatus: responder identity '%s' matches none of the "
|
||||
"%d configured peers -- per-peer connection status falls "
|
||||
"through to the non-self sourcing for every configured "
|
||||
"peer (check this node's --peers/name configuration)",
|
||||
st->responderName, n);
|
||||
}
|
||||
|
||||
if (is_primary)
|
||||
mylock_lock(&repl->lock);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
xce_ctrlpeer_t* p = (xce_ctrlpeer_t*)calloc(1, sizeof(*p));
|
||||
p->name = mc->peers_cfg.candidates[i].name;
|
||||
p->host = mc->peers_cfg.candidates[i].hostname;
|
||||
p->port = (uint32_t)mc->peers_cfg.candidates[i].port;
|
||||
|
||||
if (strcmp(mc->peers_cfg.candidates[i].name,
|
||||
st->responderName) == 0) {
|
||||
p->status = 1u; /* self: always ok (Req 13.2) */
|
||||
} else if (is_primary) {
|
||||
/* Reuses repl_active_standby_count's exact ep != NULL &&
|
||||
* !in_catch_up predicate (repl.c) rather than inventing a
|
||||
* new one (Locked Decision 3). */
|
||||
uint8_t status = 3u; /* default: no matching live slot */
|
||||
for (int j = 0; j < MAX_STANDBYS; j++) {
|
||||
if (strcmp(repl->standbys[j].host,
|
||||
mc->peers_cfg.candidates[i].hostname) == 0 &&
|
||||
repl->standbys[j].port == p->port) {
|
||||
if (repl->standbys[j].ep == NULL) {
|
||||
status = 3u; /* matched slot, no live conn */
|
||||
} else if (atomic_load_explicit(
|
||||
&repl->standbys[j].in_catch_up,
|
||||
memory_order_acquire)) {
|
||||
status = 2u; /* catching-up */
|
||||
} else {
|
||||
status = 1u; /* ok */
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
p->status = status;
|
||||
} else {
|
||||
p->status = 0u; /* unknown: only the primary can see peers */
|
||||
}
|
||||
|
||||
st->peers.list[i] = p;
|
||||
}
|
||||
|
||||
if (is_primary)
|
||||
mylock_unlock(&repl->lock);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue