feat(libxce-failover): reconnect/rejoin hook — public setter + post-reconnect invocation before resume (task 4.1)

This commit is contained in:
Rongsong Shen 2026-06-17 03:46:05 +00:00
parent 12284bc3f5
commit 8783ce5a9d
6 changed files with 264 additions and 26 deletions

View file

@ -189,6 +189,25 @@ int xcecli_ctx_recover(api_ctx_t* ac)
return 0;
}
/* ════════════════════════════════════════════════════════════════════════
* ReconnectRejoinHook — invoke the consumer rejoin callback (1.2)
* ════════════════════════════════════════════════════════════════════════ */
int xcecli_invoke_rejoin(api_ctx_t* ac)
{
if (ac == NULL)
return -EINVAL;
/* Pure client: no hook registered → nothing to do, resume directly. */
if (ac->failover.rejoin_cb == NULL)
return 0;
/* Stateful consumer: run its rejoin/state-exchange on the calling thread.
* Its rc is propagated verbatim — 0 to proceed, <0 to signal the join was
* rejected so the wrapper treats this attempt as failed (no resume). */
return ac->failover.rejoin_cb((xcecli_ctx_t)ac, ac->failover.rejoin_arg);
}
/* ════════════════════════════════════════════════════════════════════════
* XcecliRetryLoop — decision core (pure) + side-effecting wrapper
* ════════════════════════════════════════════════════════════════════════ */
@ -318,26 +337,44 @@ bool xcecli_retry_next(api_ctx_t* ac, int* rc, bool is_write,
int rrc = xce_failover_rotate(ac);
if (rrc < 0) {
WARN("xcecli failover: rotate failed (rc=%d); retrying anyway", rrc);
} else {
/* Reconnect succeeded: repair the ctx before the next xce_* attempt
* (CI2 ERR→RUN state reset, CI1 stale-reply drain) so the per-op guard
* passes and no prior-attempt reply is consumed (1.2, 4.4). */
xcecli_ctx_recover(ac);
return true;
}
/* Log the rotation target (7.1). xce_failover_rotate consumes the
* redirect hint when present (so the post-rotate target is not always
* the sticky master); report the sticky master cursor as a best-effort
* endpoint label, which is exact for the configured-master rotation. */
int cur = ac->failover.cur_master;
const char* host =
(ac->conf != NULL && cur >= 0 && cur < ac->conf->nmasters)
? ac->conf->masters[cur]
: "(unknown)";
int port = (ac->conf != NULL && cur >= 0 && cur < ac->conf->nmasters)
? ac->conf->master_ports[cur]
: 0;
INFO("xcecli failover: attempt %d → %s:%d", st->attempt,
host ? host : "(null)", port);
/* Reconnect succeeded: repair the ctx before the next xce_* attempt
* (CI2 ERR→RUN state reset, CI1 stale-reply drain) so the per-op guard
* passes and no prior-attempt reply is consumed (1.2, 4.4). */
xcecli_ctx_recover(ac);
/* Log the rotation target (7.1). xce_failover_rotate consumes the redirect
* hint when present (so the post-rotate target is not always the sticky
* master); report the sticky master cursor as a best-effort endpoint label,
* which is exact for the configured-master rotation. */
int cur = ac->failover.cur_master;
const char* host =
(ac->conf != NULL && cur >= 0 && cur < ac->conf->nmasters)
? ac->conf->masters[cur]
: "(unknown)";
int port = (ac->conf != NULL && cur >= 0 && cur < ac->conf->nmasters)
? ac->conf->master_ports[cur]
: 0;
INFO("xcecli failover: attempt %d → %s:%d", st->attempt,
host ? host : "(null)", port);
/* ReconnectRejoinHook (1.2): a stateful consumer re-establishes its session
* on the freshly-connected primary AFTER reconnect/recovery and BEFORE the
* interrupted op resumes. A pure client has no hook → no-op, resume now. */
int hrc = xcecli_invoke_rejoin(ac);
if (hrc < 0) {
/* The new primary rejected the daemon's join: treat this attempt as
* FAILED — do NOT resume into a primary that doesn't know the daemon.
* Reclassify the rejoin failure as a transport-style failover cause so
* the loop rotates/backs off again on the next iteration (subject to the
* same retry budget). When the budget is exhausted decide() returns
* false and *rc carries the last cause. */
WARN("xcecli failover: rejoin hook failed (rc=%d); rotating again",
hrc);
*rc = hrc;
return xcecli_retry_next(ac, rc, is_write, st);
}
return true;