feat(libxce-failover): reconnect/rejoin hook — public setter + post-reconnect invocation before resume (task 4.1)
This commit is contained in:
parent
12284bc3f5
commit
8783ce5a9d
6 changed files with 264 additions and 26 deletions
|
|
@ -1127,6 +1127,151 @@ static void test_recover_null_ctx_safe(void)
|
|||
printf("test_recover_null_ctx_safe: PASS\n");
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════════════
|
||||
* Task 4.1 — ReconnectRejoinHook (xcecli_set_reconnect_handler +
|
||||
* xcecli_invoke_rejoin) RED→GREEN tests.
|
||||
*
|
||||
* libxce owns the hook MECHANISM + ORDERING (reconnect → hook → resume); the
|
||||
* callback CONTENT is the consumer's. The failover machinery invokes the
|
||||
* registered callback once per successful reconnect, AFTER reconnect/recovery
|
||||
* and BEFORE the interrupted op resumes. A hook returning <0 means the new
|
||||
* primary rejected the daemon's join → treat it as a FAILED attempt (do not
|
||||
* resume; the loop rotates/backs off again). Pure clients register NO hook
|
||||
* (NULL) → invoke is a no-op returning 0 and the loop resumes directly.
|
||||
*
|
||||
* xcecli_invoke_rejoin is socket-free (it only calls the stored cb), so it is
|
||||
* unit-testable directly here without a live server.
|
||||
*
|
||||
* Requirements: 1.2 (re-establish session against new primary before resume).
|
||||
* ════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* A test rejoin callback: records that it ran (and the ctx/arg it saw) and
|
||||
* returns a caller-controlled rc so we can drive the success and failed-join
|
||||
* paths. */
|
||||
typedef struct {
|
||||
int called; /* number of times the hook fired */
|
||||
xcecli_ctx_t seen_ctx; /* the ctx the hook observed */
|
||||
void *seen_arg; /* the opaque arg the hook observed */
|
||||
int ret; /* rc the hook returns (0 ok, <0 failed rejoin) */
|
||||
} fo_rejoin_probe_t;
|
||||
|
||||
static int fo_rejoin_cb(xcecli_ctx_t ctx, void *arg)
|
||||
{
|
||||
fo_rejoin_probe_t *p = (fo_rejoin_probe_t *)arg;
|
||||
p->called++;
|
||||
p->seen_ctx = ctx;
|
||||
p->seen_arg = arg;
|
||||
return p->ret;
|
||||
}
|
||||
|
||||
/* Req 1.2: the setter stores cb+arg into the failover state; invoke then calls
|
||||
* the registered cb exactly once, passing the opaque ctx and arg, and returns
|
||||
* the cb's rc (0 on a successful rejoin). */
|
||||
static void test_invoke_rejoin_calls_registered_hook(void)
|
||||
{
|
||||
xce_client_config_t conf;
|
||||
api_ctx_t ac;
|
||||
fo_make_client_masters(&conf);
|
||||
fo_make_api_ctx(&ac, &conf);
|
||||
|
||||
fo_rejoin_probe_t probe = { 0 };
|
||||
probe.ret = 0;
|
||||
|
||||
/* setter stores cb+arg on the failover state */
|
||||
int src = xcecli_set_reconnect_handler((xcecli_ctx_t)&ac, fo_rejoin_cb,
|
||||
&probe);
|
||||
TEST_ASSERT_EQUAL_INT(0, src);
|
||||
TEST_ASSERT_TRUE(ac.failover.rejoin_cb == fo_rejoin_cb);
|
||||
TEST_ASSERT_TRUE(ac.failover.rejoin_arg == &probe);
|
||||
|
||||
/* invoke fires the hook once, returns its rc (0), passes ctx + arg */
|
||||
int rc = xcecli_invoke_rejoin(&ac);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, probe.called);
|
||||
TEST_ASSERT_TRUE(probe.seen_ctx == (xcecli_ctx_t)&ac);
|
||||
TEST_ASSERT_TRUE(probe.seen_arg == &probe);
|
||||
|
||||
printf("test_invoke_rejoin_calls_registered_hook: PASS\n");
|
||||
}
|
||||
|
||||
/* Req 1.2: a hook that returns <0 (the new primary rejected the join) makes
|
||||
* invoke return that <0 — the wrapper treats it as a failed attempt and does
|
||||
* NOT resume the interrupted op. */
|
||||
static void test_invoke_rejoin_propagates_failure(void)
|
||||
{
|
||||
xce_client_config_t conf;
|
||||
api_ctx_t ac;
|
||||
fo_make_client_masters(&conf);
|
||||
fo_make_api_ctx(&ac, &conf);
|
||||
|
||||
fo_rejoin_probe_t probe = { 0 };
|
||||
probe.ret = -ECONNREFUSED; /* join rejected by new primary */
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
0, xcecli_set_reconnect_handler((xcecli_ctx_t)&ac, fo_rejoin_cb,
|
||||
&probe));
|
||||
|
||||
int rc = xcecli_invoke_rejoin(&ac);
|
||||
TEST_ASSERT_EQUAL_INT(-ECONNREFUSED, rc); /* failure surfaced to the loop */
|
||||
TEST_ASSERT_EQUAL_INT(1, probe.called);
|
||||
|
||||
printf("test_invoke_rejoin_propagates_failure: PASS\n");
|
||||
}
|
||||
|
||||
/* Req 1.2: a pure client registers NO hook (NULL) → invoke is a no-op that
|
||||
* returns 0 and calls nothing, so the loop resumes directly. */
|
||||
static void test_invoke_rejoin_no_hook_is_noop(void)
|
||||
{
|
||||
xce_client_config_t conf;
|
||||
api_ctx_t ac;
|
||||
fo_make_client_masters(&conf);
|
||||
fo_make_api_ctx(&ac, &conf); /* leaves rejoin_cb == NULL */
|
||||
|
||||
TEST_ASSERT_NULL(ac.failover.rejoin_cb);
|
||||
|
||||
int rc = xcecli_invoke_rejoin(&ac);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc); /* no hook → no-op success */
|
||||
|
||||
printf("test_invoke_rejoin_no_hook_is_noop: PASS\n");
|
||||
}
|
||||
|
||||
/* Req 1.2: the setter is NULL-safe on a NULL ctx, and can clear a previously
|
||||
* registered hook by passing NULL (back to the pure-client no-op path). */
|
||||
static void test_set_reconnect_handler_null_safe_and_clears(void)
|
||||
{
|
||||
xce_client_config_t conf;
|
||||
api_ctx_t ac;
|
||||
fo_make_client_masters(&conf);
|
||||
fo_make_api_ctx(&ac, &conf);
|
||||
|
||||
/* NULL ctx rejected without crashing */
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL,
|
||||
xcecli_set_reconnect_handler(NULL, fo_rejoin_cb,
|
||||
NULL));
|
||||
|
||||
fo_rejoin_probe_t probe = { 0 };
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
0, xcecli_set_reconnect_handler((xcecli_ctx_t)&ac, fo_rejoin_cb,
|
||||
&probe));
|
||||
TEST_ASSERT_TRUE(ac.failover.rejoin_cb == fo_rejoin_cb);
|
||||
|
||||
/* clearing with a NULL cb restores the no-hook no-op path */
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
0, xcecli_set_reconnect_handler((xcecli_ctx_t)&ac, NULL, NULL));
|
||||
TEST_ASSERT_NULL(ac.failover.rejoin_cb);
|
||||
TEST_ASSERT_EQUAL_INT(0, xcecli_invoke_rejoin(&ac));
|
||||
TEST_ASSERT_EQUAL_INT(0, probe.called); /* cleared hook never fired */
|
||||
|
||||
printf("test_set_reconnect_handler_null_safe_and_clears: PASS\n");
|
||||
}
|
||||
|
||||
/* Guard: invoke on a NULL api_ctx is rejected without crashing. */
|
||||
static void test_invoke_rejoin_null_ctx_safe(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, xcecli_invoke_rejoin(NULL));
|
||||
printf("test_invoke_rejoin_null_ctx_safe: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Suite entry point ──────────────────────────────────────────────── */
|
||||
|
||||
void test_libxce_retry(void)
|
||||
|
|
@ -1165,5 +1310,10 @@ void test_libxce_retry(void)
|
|||
RUN_TEST(test_recover_leaves_down_state_untouched);
|
||||
RUN_TEST(test_recover_drains_stale_wbuf_entry);
|
||||
RUN_TEST(test_recover_null_ctx_safe);
|
||||
RUN_TEST(test_invoke_rejoin_calls_registered_hook);
|
||||
RUN_TEST(test_invoke_rejoin_propagates_failure);
|
||||
RUN_TEST(test_invoke_rejoin_no_hook_is_noop);
|
||||
RUN_TEST(test_set_reconnect_handler_null_safe_and_clears);
|
||||
RUN_TEST(test_invoke_rejoin_null_ctx_safe);
|
||||
UNITY_END();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue