feat: implement push_config_to_standbys() — parallel fan-out via xce_replconfigpush_req_raw() using capn_new_list8 for raw bytes

This commit is contained in:
Rongsong Shen 2026-05-14 03:58:02 +00:00
parent 609314efa4
commit 2e2aaea78c
3 changed files with 190 additions and 26 deletions

View file

@ -773,39 +773,109 @@ static void send_upgrade_error(endpoint_t *ep, xce_request_t *req,
/* ── push_config_to_standbys ─────────────────────── */
/*
* push_config_to_standbys: fan-out new config blob to connected standbys.
*
* Task 11 dependency: the OPCode_replConfigPush wire protocol requires the
* repl_config_push_req_t.sha256 and .bytes fields to carry raw byte data.
* In the current typemap these fields are typed as xce_data_t* (a discriminated
* cluster-data union), not a raw byte buffer. The capnp Data-type shim that
* maps them to plain byte arrays is wired in Task 11.
*
* Until Task 11 lands, standbys catch up automatically via handle_config_request()
* on the next connection. The primary ACKs acked_count=0; the admin tool knows
* to wait for standbys to reconnect and pull.
*/
typedef struct {
endpoint_t *ep;
xce_peerinfo_t *from;
uint64_t new_tag;
const uint8_t *bytes;
size_t blen;
const uint8_t *sha256;
atomic_int *stop;
int acked;
} push_ctx_t;
static void *push_thread(void *arg)
{
push_ctx_t *ctx = (push_ctx_t *)arg;
if (atomic_load(ctx->stop))
return NULL;
int rc = xce_replconfigpush_req_raw(
ctx->ep, ctx->from,
ctx->new_tag,
ctx->sha256, 32,
ctx->bytes, ctx->blen);
if (rc == 0)
ctx->acked = 1;
else
WARN("push_config_to_standbys: push to endpoint %p failed: %d", ctx->ep, rc);
return NULL;
}
int push_config_to_standbys(cluster_t *c, const uint8_t *bytes, size_t blen,
const uint8_t sha256[32], uint64_t new_tag)
{
(void)bytes;
(void)blen;
(void)sha256;
(void)new_tag;
if (!c->ha.standby_endpoints)
return 0;
int n = 0;
if (c->ha.standby_endpoints) {
pthread_mutex_lock(&c->ha.lock);
n = c->ha.standby_endpoints->count;
/* Snapshot connected standbys under lock */
pthread_mutex_lock(&c->ha.lock);
int n = c->ha.standby_endpoints->count;
if (n == 0) {
pthread_mutex_unlock(&c->ha.lock);
return 0;
}
endpoint_t **eps = malloc((size_t)n * sizeof(endpoint_t *));
if (!eps) {
pthread_mutex_unlock(&c->ha.lock);
return 0;
}
for (int i = 0; i < n; i++)
eps[i] = (endpoint_t *)zarray_get(c->ha.standby_endpoints, i);
pthread_mutex_unlock(&c->ha.lock);
atomic_int stop = 0;
push_ctx_t *ctxs = calloc((size_t)n, sizeof(push_ctx_t));
pthread_t *threads = malloc((size_t)n * sizeof(pthread_t));
if (!ctxs || !threads) {
free(ctxs); free(threads); free(eps);
return 0;
}
if (n > 0)
WARN("push_config_to_standbys: %d standby(s) connected but push"
" deferred to Task 11 (typemap byte shims not yet wired);"
" standbys will pull on reconnect", n);
return 0;
for (int i = 0; i < n; i++) {
ctxs[i].ep = eps[i];
ctxs[i].from = c->myhost;
ctxs[i].new_tag = new_tag;
ctxs[i].bytes = bytes;
ctxs[i].blen = blen;
ctxs[i].sha256 = sha256;
ctxs[i].stop = &stop;
ctxs[i].acked = 0;
pthread_create(&threads[i], NULL, push_thread, &ctxs[i]);
}
/* Aggregate wait: CONF_PUSH_TIMEOUT_MS total, not per-standby */
struct timespec deadline;
clock_gettime(CLOCK_MONOTONIC, &deadline);
deadline.tv_sec += CONF_PUSH_TIMEOUT_MS / 1000;
deadline.tv_nsec += (CONF_PUSH_TIMEOUT_MS % 1000) * 1000000L;
if (deadline.tv_nsec >= 1000000000L) {
deadline.tv_sec++;
deadline.tv_nsec -= 1000000000L;
}
while (1) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if (now.tv_sec > deadline.tv_sec ||
(now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec))
break;
struct timespec sl = { .tv_sec = 0, .tv_nsec = 5 * 1000000L };
nanosleep(&sl, NULL);
}
atomic_store(&stop, 1);
int acked = 0;
for (int i = 0; i < n; i++) {
pthread_join(threads[i], NULL);
if (ctxs[i].acked) acked++;
}
INFO("Config pushed to %d/%d standbys", acked, n);
free(ctxs); free(threads); free(eps);
return acked;
}
/* ── handle_upgrade_config ──────────────────────── */