fix(libxce-failover): known-dead ctx (XCE_CTX_ERR) triggers reconnect instead of surfacing -EINVAL

Live 3-node E2E revealed the reported 'Invalid argument' bug was still
reachable: after the primary dies, handle_disconnect leaves ctx.state=
XCE_CTX_ERR, so a FRESH xce_* call returns -EINVAL from the xceapi guard
before reaching a primary. The classifier mapped -EINVAL to TERMINAL, so the
loop bailed without rotating/recovering. decide() now detects the known-dead
ctx and treats it as a prompt reconnect cause (RETRY_NOW), surfacing the
distinct cluster-not-ready code on budget exhaustion, never -EINVAL.
Regression test: test_decide_known_dead_ctx_retries_not_terminal.
This commit is contained in:
Rongsong Shen 2026-06-17 04:46:01 +00:00
parent c616f41364
commit fa65e794ec
2 changed files with 63 additions and 0 deletions

View file

@ -271,6 +271,28 @@ bool xcecli_retry_decide(api_ctx_t* ac, int* rc, bool is_write,
if (ac == NULL || rc == NULL || cfg == NULL || st == NULL)
return false;
/* Known-dead connection (the reported bug). After the primary dies,
* handle_disconnect leaves ctx.state == XCE_CTX_ERR, so a FRESH xce_* call
* hits the per-op xceapi guard and returns -EINVAL *before* the op reaches a
* primary — and -EINVAL would otherwise classify as TERMINAL and surface the
* opaque "Invalid argument". Detect the dead-ctx state directly and treat it
* as a prompt transport-style failover cause (reconnect via rotate+recover),
* regardless of *rc. After the next successful reconnect the ctx returns to
* RUN and subsequent attempts classify normally (success / no-primary /
* transport). On budget exhaustion we never reached a primary → surface the
* distinct cluster-not-ready code, never -EINVAL. */
if (ac->ctx.state == XCE_CTX_ERR) {
if (action_out != NULL)
*action_out = XCE_FO_RETRY_NOW;
st->attempt++;
if (st->attempt > cfg->max_retries) {
*rc = -E_XCE_NOT_READY;
return false;
}
st->delay_ms = 0;
return true;
}
xce_fo_action_t action =
xce_failover_classify(*rc, is_write, st->attempt);
if (action_out != NULL)