feat(libxce-failover): retry decision loop — pure decide (budget/backoff/jitter, D6 exhaustion) + thin next wrapper (task 3.1)
This commit is contained in:
parent
cec2e647dc
commit
84c5e32c2c
4 changed files with 481 additions and 2 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#define _CLIENT_INTERNAL_H_ 1
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
#include "mylock.h"
|
||||
#include "netio.h"
|
||||
#include "request.h"
|
||||
#include "xce/client_failover.h" /* xce_fo_action_t */
|
||||
#include "xcectx.h"
|
||||
|
||||
/*
|
||||
|
|
@ -80,4 +82,61 @@ 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);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue