feat(ha-config-bootstrap): implement xce_retry_config_t and xce_request_with_retry (tasks 10.1, 10.2)
This commit is contained in:
parent
bb8e531a12
commit
0ec0ffa7e3
4 changed files with 163 additions and 12 deletions
|
|
@ -5,6 +5,7 @@
|
|||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include "xce/errnox.h" /* E_XCE_NOT_READY, E_XCE_STANDBY */
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
|
|
@ -1497,3 +1498,110 @@ int xce_clear_reason_req(endpoint_t* ep, xce_clear_reason_t* reason)
|
|||
|
||||
return xce_request(ep, &request);
|
||||
}
|
||||
|
||||
/* ── Task 10.2: Client-Side Transparent Retry (Req 7.1–7.6) ────────────────
|
||||
*
|
||||
* xce_retry_send_hook: test-injectable send+receive round-trip.
|
||||
* NULL in production; set by unit tests to return controlled replies.
|
||||
*/
|
||||
int (*xce_retry_send_hook)(endpoint_t *ep, xce_request_t *req,
|
||||
xce_reply_t *reply_out) = NULL;
|
||||
|
||||
const xce_retry_config_t XCE_RETRY_DEFAULTS = {
|
||||
.max_retries = 20,
|
||||
.initial_retry_ms = 50,
|
||||
.max_retry_ms = 5000,
|
||||
.backoff_multiplier = 2.0,
|
||||
};
|
||||
|
||||
/* is_non_retriable: errors that should surface immediately without retry */
|
||||
static int is_non_retriable(int status)
|
||||
{
|
||||
return (status == -EPERM || status == -EACCES ||
|
||||
status == -EINVAL || status == -ENOENT);
|
||||
}
|
||||
|
||||
/* ms_sleep_jittered: sleep delay_ms + random jitter via rand_r (Req 7.6) */
|
||||
static void ms_sleep_jittered(int delay_ms, unsigned int *seed)
|
||||
{
|
||||
if (delay_ms <= 0) return;
|
||||
int jitter_ms = (int)((rand_r(seed) % (delay_ms / 4 + 1)));
|
||||
long total_ns = ((long)delay_ms + jitter_ms) * 1000000L;
|
||||
struct timespec ts = { .tv_sec = total_ns / 1000000000L,
|
||||
.tv_nsec = total_ns % 1000000000L };
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
|
||||
int xce_request_with_retry(endpoint_t *ep,
|
||||
xce_request_t *req,
|
||||
const xce_retry_config_t *cfg)
|
||||
{
|
||||
if (cfg == NULL)
|
||||
cfg = &XCE_RETRY_DEFAULTS;
|
||||
|
||||
unsigned int rand_seed = (unsigned int)(uintptr_t)ep ^ (unsigned int)time(NULL);
|
||||
int delay_ms = cfg->initial_retry_ms;
|
||||
int last_rc = -ETIMEDOUT;
|
||||
|
||||
for (int attempt = 0; attempt <= cfg->max_retries; attempt++) {
|
||||
xce_reply_t reply;
|
||||
memset(&reply, 0, sizeof(reply));
|
||||
|
||||
int rc;
|
||||
if (xce_retry_send_hook) {
|
||||
rc = xce_retry_send_hook(ep, req, &reply);
|
||||
} else {
|
||||
/* Production path: fire-and-forget send; reply handling is caller's
|
||||
* responsibility via the event loop. Return send error directly. */
|
||||
rc = xce_request(ep, req);
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
return 0; /* async: caller reads reply via event loop */
|
||||
}
|
||||
|
||||
/* Transport-level failure (ECONNREFUSED, ETIMEDOUT) → retry (Req 7.3) */
|
||||
if (rc == -ECONNREFUSED || rc == -ETIMEDOUT) {
|
||||
last_rc = rc;
|
||||
if (attempt < cfg->max_retries)
|
||||
ms_sleep_jittered(delay_ms, &rand_seed);
|
||||
goto next_backoff;
|
||||
}
|
||||
|
||||
/* Any other transport error → surface immediately */
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
|
||||
/* Application-layer status from reply */
|
||||
int status = (int)reply.status;
|
||||
|
||||
if (status == 0)
|
||||
return 0; /* success */
|
||||
|
||||
last_rc = status;
|
||||
|
||||
/* Non-retriable errors: surface immediately (Req 7.4) */
|
||||
if (is_non_retriable(status))
|
||||
return status;
|
||||
|
||||
/* XCE_ERR_STANDBY with redirect → reconnect (Req 7.2).
|
||||
* In hook mode, the hook already updated ep/redirect state.
|
||||
* Just retry; redirect handling is at the transport layer. */
|
||||
if (status == -E_XCE_STANDBY && reply.redirect_host != NULL
|
||||
&& reply.redirect_port != 0) {
|
||||
/* no extra sleep for redirect — just retry immediately */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* XCE_ERR_NOT_READY or XCE_ERR_STANDBY without redirect → backoff */
|
||||
if (attempt < cfg->max_retries)
|
||||
ms_sleep_jittered(delay_ms, &rand_seed);
|
||||
|
||||
next_backoff:
|
||||
{
|
||||
int next = (int)((double)delay_ms * cfg->backoff_multiplier);
|
||||
delay_ms = next > cfg->max_retry_ms ? cfg->max_retry_ms : next;
|
||||
}
|
||||
}
|
||||
|
||||
return last_rc;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue