feat(ha-wal-replication): HA→replicator standby registration bridge (tasks 2.1-2.3)

Register standbys with replicator on connect (election promotion +
late-joiner), deregister on disconnect (vote err handler + standby err
handler). repl_add_standby now idempotent (duplicate ep returns
existing slot).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-06-10 08:35:02 +00:00
parent 18e351ee1b
commit 81dbc4d7b4
4 changed files with 542 additions and 18 deletions

View file

@ -12,6 +12,15 @@
#include "repl.h"
#include "mylock.h"
#include "utils.h"
#include "request.h" /* xce_wal_batch_req_raw */
#include "typemap.h" /* wal_catchup_entry_t */
#ifdef XCE_TESTING
/* Test-injectable send hook: replaces xce_wal_batch_req_raw in unit tests. */
int (*repl_broadcast_send_hook)(void *ep,
wal_catchup_entry_t **entries,
int n_entries) = NULL;
#endif
/* ============================================================
* Lifecycle
@ -69,6 +78,15 @@ int repl_add_standby(replicator_t *r, repl_ep_t ep, const char *host, uint32_t p
mylock_lock(&r->lock);
/* Idempotency guard: if ep is already registered, return its slot index.
* Req 1.3: duplicate repl_add_standby calls are treated as no-ops. */
for (int i = 0; i < MAX_STANDBYS; i++) {
if (r->standbys[i].ep == ep) {
mylock_unlock(&r->lock);
return i;
}
}
if (r->nstandbys >= MAX_STANDBYS) {
mylock_unlock(&r->lock);
return -ENOSPC;
@ -187,15 +205,11 @@ void repl_on_ack(replicator_t *r, repl_ep_t ep, uint64_t acked_lsn)
* Iterates active standbys; skips empty slots and standbys in catch-up.
* Evicts standbys whose pending_ack_count exceeds MAX_PENDING_ACKS by marking
* them in_catch_up=1 (inlined to avoid re-entrant lock acquisition).
* TCP send wiring deferred to task 7.6.
* ============================================================ */
int repl_broadcast(replicator_t *r, const wal_entry_hdr_t *hdr,
const void *payload, uint32_t payload_len)
{
(void)payload;
(void)payload_len;
/* WAL_CHECKPOINT entries must never be replicated */
assert(hdr != NULL);
assert(hdr->type != WAL_CHECKPOINT);
@ -225,7 +239,33 @@ int repl_broadcast(replicator_t *r, const wal_entry_hdr_t *hdr,
continue;
}
/* Non-evicted standby: track the pending send (TCP wiring in task 7.6) */
/* Build a single-entry batch and enqueue it for this standby. */
wal_catchup_entry_t entry;
memset(&entry, 0, sizeof(entry));
entry.lsn = hdr->lsn;
entry.entry_type = (uint8_t)hdr->type;
entry.raw_payload = (uint8_t *)(uintptr_t)payload;
entry.raw_payload_len = payload_len;
wal_catchup_entry_t *ep_arr[1] = { &entry };
int rc;
#ifdef XCE_TESTING
if (repl_broadcast_send_hook)
rc = repl_broadcast_send_hook(s->ep, ep_arr, 1);
else
rc = xce_wal_batch_req_raw(s->ep, ep_arr, 1);
#else
rc = xce_wal_batch_req_raw(s->ep, ep_arr, 1);
#endif
if (rc != 0) {
WARN("repl_broadcast: send to standby %s:%u failed (rc=%d) — continuing",
s->host, s->port, rc);
continue;
}
TRACE("repl_broadcast: sent lsn=%lu type=%u to %s:%u",
(unsigned long)hdr->lsn, hdr->type, s->host, s->port);
s->pending_ack_count++;
}