forked from lei.chen/myxce
178 lines
7.5 KiB
C
178 lines
7.5 KiB
C
#if !defined(_CLIENT_INTERNAL_H_)
|
|
|
|
#define _CLIENT_INTERNAL_H_ 1
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include "client.h"
|
|
#include "mylock.h"
|
|
#include "netio.h"
|
|
#include "request.h"
|
|
#include "xce/client_failover.h" /* xce_fo_action_t */
|
|
#include "xcectx.h"
|
|
|
|
/*
|
|
* client_internal.h — private libxce client state shared between client.c and
|
|
* the forthcoming failover helper (client_failover.c, task 1.x).
|
|
*
|
|
* api_ctx_t was previously file-local to client.c. It is moved here so the
|
|
* failover helper can operate on it without exposing it through the public
|
|
* API in client.h.
|
|
*/
|
|
|
|
typedef enum {
|
|
API_CTX_INIT = 0,
|
|
API_CTX_RUN,
|
|
API_CTX_DOWN,
|
|
API_CTX_ERR
|
|
} api_ctx_state_t;
|
|
|
|
/*
|
|
* xcecli_reconnect_cb (consumer rejoin hook) and its public setter
|
|
* xcecli_set_reconnect_handler() are declared in the public client.h (included
|
|
* above) as of task 4.1. The failover state below carries the registered hook.
|
|
*/
|
|
|
|
/*
|
|
* xce_failover_state_t: per-context failover state. Persists across calls so a
|
|
* sticky primary and the retry tunables survive between requests.
|
|
*
|
|
* lock — serializes xcecli_* per ctx (shared-ctx safety)
|
|
* cur_master — index into conf->masters[] (sticky primary)
|
|
* retry — defaults from XCE_RETRY_DEFAULTS unless overridden
|
|
* rejoin_cb — consumer rejoin hook; NULL for pure clients
|
|
* rejoin_arg — opaque argument passed to rejoin_cb
|
|
*/
|
|
typedef struct {
|
|
mylock_t lock;
|
|
int cur_master;
|
|
xce_retry_config_t retry;
|
|
xcecli_reconnect_cb rejoin_cb;
|
|
void* rejoin_arg;
|
|
} xce_failover_state_t;
|
|
|
|
typedef struct {
|
|
xce_client_config_t* conf;
|
|
endpoint_t ep;
|
|
xce_ctx_t ctx;
|
|
xce_failover_state_t failover;
|
|
} api_ctx_t;
|
|
|
|
/*
|
|
* EndpointRotator (task 2.2) — these operate on the private api_ctx_t, so they
|
|
* live in this internal header rather than the public xce/client_failover.h.
|
|
*
|
|
* xce_failover_next_target(): PURE target-selection (no I/O). If a redirect-
|
|
* address hint is set on ac->ctx, write it out and CONSUME it (one-shot,
|
|
* advisory). Otherwise advance the sticky master cursor (ac->failover.cur_master)
|
|
* to the next configured master in ac->conf, wrapping around. Returns 0,
|
|
* -EINVAL on a NULL/zero argument, or -ENOENT if no masters and no hint.
|
|
* Correctness never depends on the hint.
|
|
*
|
|
* xce_failover_rotate(): close the stale connection on ac->ep and reconnect the
|
|
* SAME endpoint object to the chosen target. Thin wrapper over the selector +
|
|
* endpoint_connect(); the live socket path is exercised by the E2E task.
|
|
*/
|
|
int xce_failover_next_target(api_ctx_t* ac, char* host_out, size_t host_sz,
|
|
uint16_t* port_out);
|
|
int xce_failover_rotate(api_ctx_t* ac);
|
|
|
|
/*
|
|
* XcecliRetryLoop / decision helper (task 3.1) — see design.md
|
|
* "Components and Interfaces → xcecli_retry_next".
|
|
*
|
|
* xcecli_retry_t: per-op retry carrier. One instance lives on the caller's
|
|
* stack for the duration of a single xcecli_* op's do/while loop.
|
|
* attempt — number of re-sends so far (0 on the first send).
|
|
* delay_ms — backoff to apply BEFORE the next attempt (0 for RETRY_NOW).
|
|
* rand_seed — per-op rand_r() state for jittered backoff (6.3).
|
|
*/
|
|
typedef struct {
|
|
int attempt;
|
|
int delay_ms;
|
|
unsigned int rand_seed;
|
|
} xcecli_retry_t;
|
|
|
|
/*
|
|
* XCECLI_RETRY_INIT(ac): initialise a retry carrier from an api_ctx_t. The seed
|
|
* mixes the ctx pointer so concurrent ops on distinct ctxs jitter independently.
|
|
*/
|
|
#define XCECLI_RETRY_INIT(ac) \
|
|
((xcecli_retry_t){ .attempt = 0, \
|
|
.delay_ms = 0, \
|
|
.rand_seed = (unsigned int)(uintptr_t)(ac) })
|
|
|
|
/*
|
|
* xcecli_retry_decide(): the PURE decision core (no I/O, no sleep, no socket,
|
|
* no logging) so it is unit-testable. Given the rc returned by the last xce_*
|
|
* attempt, it:
|
|
* - classifies *rc via xce_failover_classify(*rc, is_write, st->attempt);
|
|
* - on SUCCESS applies the DUP-replay rule (sets *rc = 0) and returns false;
|
|
* - on TERMINAL returns false leaving *rc as-is;
|
|
* - on a retriable action advances st->attempt, computes st->delay_ms
|
|
* (0 for RETRY_NOW; a jittered exponential backoff bounded by
|
|
* cfg->max_retry_ms for RETRY_BACKOFF), reports the action in *action_out,
|
|
* and returns true — UNLESS the retry budget (cfg->max_retries) is now
|
|
* exhausted, in which case it returns false and, for a no-primary cause,
|
|
* sets *rc = -E_XCE_NOT_READY (a transport-only cause keeps the last rc).
|
|
*
|
|
* It does NOT reset ctx state, drain wbuf, sleep, rotate, or invoke the rejoin
|
|
* hook — those are the side-effecting wrapper's job (xcecli_retry_next) and
|
|
* later tasks (3.2 state-reset/drain, 4.1 rejoin hook).
|
|
*/
|
|
bool xcecli_retry_decide(api_ctx_t* ac, int* rc, bool is_write,
|
|
const xce_retry_config_t* cfg, xcecli_retry_t* st,
|
|
xce_fo_action_t* action_out);
|
|
|
|
/*
|
|
* xcecli_retry_next(): the side-effecting wrapper the xcecli_* do/while loops
|
|
* call. = xcecli_retry_decide() + (RETRY_BACKOFF: a real st->delay_ms sleep) +
|
|
* (any retry: xce_failover_rotate(ac) + INFO log of the rotation target, 7.1) +
|
|
* (budget exhaustion: WARN log of the attempt count, 7.2). Returns true → the
|
|
* caller re-invokes its xce_* op; false → stop, *rc is final.
|
|
*/
|
|
bool xcecli_retry_next(api_ctx_t* ac, int* rc, bool is_write,
|
|
xcecli_retry_t* st);
|
|
|
|
/*
|
|
* xcecli_ctx_recover (task 3.2) — repair the inner xce_ctx_t after a successful
|
|
* reconnect so the next xce_* attempt can proceed. Socket-free (no I/O, no
|
|
* sleep) so it is unit-testable; xcecli_retry_next invokes it after a successful
|
|
* xce_failover_rotate(), before re-sending the interrupted op.
|
|
*
|
|
* CI2 (state reset): the transport-error path left ctx->state = XCE_CTX_ERR
|
|
* (xcectx.c:174), which the per-op XCE_CTX_RUN guard rejects as -EINVAL
|
|
* (xceapi.c) — the reported bug. recover restores XCE_CTX_RUN, but ONLY from
|
|
* ERR: a DOWN/INIT ctx (deliberately stopped / not yet started) is left
|
|
* as-is, and a RUN ctx is already fine.
|
|
* CI1 (stale-reply drain): handle_disconnect may have injected a stale status
|
|
* into the single-slot ctx->wbuf (xcectx.c:180). recover drains any pending
|
|
* entry with a non-blocking cbuf_tryget so the next attempt's cbuf_get
|
|
* cannot consume a prior-attempt reply. Reconnect closes the old fd first,
|
|
* so the dead connection yields no further replies — this drain is enough.
|
|
*
|
|
* Returns 0 on success, -EINVAL on a NULL ac.
|
|
*/
|
|
int xcecli_ctx_recover(api_ctx_t* ac);
|
|
|
|
/*
|
|
* xcecli_invoke_rejoin (task 4.1) — invoke the consumer rejoin hook once after a
|
|
* successful reconnect/recovery and BEFORE the interrupted op resumes. Socket-
|
|
* free (it only calls the stored callback), so it is unit-testable. The
|
|
* xcecli_retry_next wrapper calls it AFTER xce_failover_rotate +
|
|
* xcecli_ctx_recover and BEFORE re-sending the op.
|
|
*
|
|
* If no hook is registered (rejoin_cb == NULL, a pure client) it is a no-op and
|
|
* returns 0 so the loop resumes directly. Otherwise it returns the callback's
|
|
* rc: 0 to proceed, <0 to treat the rejoin as FAILED (the new primary rejected
|
|
* the daemon's join) — the wrapper then does NOT resume but rotates/backs off on
|
|
* the next iteration.
|
|
*
|
|
* Returns 0 on a successful (or absent) rejoin, the hook's <0 rc on a failed
|
|
* rejoin, or -EINVAL on a NULL ac.
|
|
*/
|
|
int xcecli_invoke_rejoin(api_ctx_t* ac);
|
|
|
|
#endif
|