feat(libxce-failover): 4-action status classifier with attempt-aware DUP-replay rule (task 2.1)

This commit is contained in:
Rongsong Shen 2026-06-17 02:42:54 +00:00
parent d11c6e1d89
commit fa2ac05036
5 changed files with 222 additions and 1 deletions

60
src/lib/client_failover.c Normal file
View file

@ -0,0 +1,60 @@
/*
* client_failover.c — StatusClassifier for the libxce-failover spec (task 2.1).
*
* Pure mapping from a reply status / transport rc to a failover action. No I/O,
* no globals. See design.md "Components and Interfaces → StatusClassifier" and
* requirements 2.5, 4.1, 4.2, 4.3, 4.5, 5.2.
*/
#define _GNU_SOURCE
#include "xce/client_failover.h"
#include <errno.h>
#include "xce/errnox.h"
/*
* Controller status codes (E_XCE_*) are positive enum values, but the failover
* loop may observe them as negative -errno-style rc. Accept either sign by
* comparing against the magnitude.
*/
static int fo_abs(int v)
{
return v < 0 ? -v : v;
}
xce_fo_action_t xce_failover_classify(int status, bool is_write, int attempt)
{
/* Success: served by the primary. */
if (status == 0)
return XCE_FO_SUCCESS;
int code = fo_abs(status);
/* Idempotent-replay rule (5.2): a duplicate on a re-sent write means the
* controller's client-jobid replay guard already applied our write — treat
* it as success. On the first send (or a non-write), it is a genuine
* duplicate and is surfaced unchanged. */
if (code == E_XCE_DUP_JOB || code == EEXIST) {
if (is_write && attempt > 0)
return XCE_FO_SUCCESS;
return XCE_FO_TERMINAL;
}
/* Redirect / standby: a primary exists elsewhere, or this op is
* primary-only — rotate promptly, no backoff (4.2, 4.3). */
if (code == E_XCE_REDIRECT || code == E_XCE_STANDBY)
return XCE_FO_RETRY_NOW;
/* No primary known yet / controller initializing or electing — rotate after
* jittered backoff (4.1). */
if (code == E_XCE_NOMASTER || code == E_XCE_NOT_READY)
return XCE_FO_RETRY_BACKOFF;
/* Transport-level failures — reconnect/rotate and retry now (4.4). */
if (code == ECONNREFUSED || code == ETIMEDOUT || code == ECONNRESET)
return XCE_FO_RETRY_NOW;
/* Everything else — auth/permission/invalid/not-found and any unrecognized
* status — is non-retriable and surfaced to the caller (4.5). */
return XCE_FO_TERMINAL;
}