feat(election-iomgr-refactor): add vote_ep_ctx_t and on_vote_connected
- vote_ep_ctx_t: per-peer context (ha, c, ctx, peer_idx, term/tag/lsn/name)
- election_timeout_ctx_t: cancelled flag + context for delay watchdog
- on_vote_connected(): iomgr callback fires after SSL handshake; builds
xce_request_t{opcode=OPCode_requestVote}, calls xce_request, sets msg_handler
- vote_reply_msg_handler stub (returns 0; implemented in task 2.3)
- Test seam: xce_vote_request_hook for unit testing without live SSL
- 4 new unit tests: opcode, field encoding, msg_handler set, cancelled flag
Context note: vctx->c is NULL in test helper — task 2.3 must use real cluster_t*
This commit is contained in:
parent
0d7757fdd5
commit
18c280429e
4 changed files with 500 additions and 0 deletions
|
|
@ -1016,6 +1016,174 @@ void handle_request_vote_reply(ha_state_t *ha,
|
|||
pthread_mutex_unlock(&ctx->lock);
|
||||
}
|
||||
|
||||
/* ── Task 2.2: iomgr-native vote send path ──────────────────────────────────
|
||||
*
|
||||
* vote_ep_ctx_t: per-peer context stored in ep->data for each outgoing vote
|
||||
* endpoint. on_vote_connected fires after the TLS handshake completes (when
|
||||
* endpoint_t.ssl_handshake_done transitions to 1 in io_handler).
|
||||
*
|
||||
* election_timeout_ctx_t: argument for the delay_task_ms watchdog that fires
|
||||
* ha_elect_evaluate_and_signal when the election deadline expires without all
|
||||
* peers replying. Uses an _Atomic int cancelled flag for lock-free cancellation.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
ha_state_t *ha;
|
||||
cluster_t *c;
|
||||
ha_elect_ctx_t *ctx;
|
||||
int peer_idx;
|
||||
uint64_t my_term;
|
||||
uint64_t my_tag;
|
||||
uint64_t my_lsn;
|
||||
const char *my_name;
|
||||
} vote_ep_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
_Atomic int cancelled;
|
||||
ha_state_t *ha;
|
||||
cluster_t *c;
|
||||
ha_elect_ctx_t *ctx;
|
||||
} election_timeout_ctx_t;
|
||||
|
||||
/* xce_request: defined in request.c, not yet in any public header */
|
||||
extern int xce_request(endpoint_t *ep, xce_request_t *req);
|
||||
|
||||
/*
|
||||
* vote_reply_msg_handler: forward declaration.
|
||||
* Implemented in task 2.3. Registered on the endpoint by on_vote_connected
|
||||
* so that the next iomgr EPOLLIN delivers the vote reply to this handler.
|
||||
* Signature matches msg_handler_t = int (*)(void*) from netio.h.
|
||||
*/
|
||||
static int vote_reply_msg_handler(void *ep);
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/*
|
||||
* xce_vote_request_hook: test-only seam.
|
||||
*
|
||||
* When non-NULL, on_vote_connected() calls this INSTEAD OF the real
|
||||
* xce_request() so tests can capture the call without requiring a live
|
||||
* iomgr or SSL endpoint. The hook receives the fully built
|
||||
* xce_request_t and may inspect all fields.
|
||||
*
|
||||
* Pattern mirrors xce_retry_send_hook in request.c.
|
||||
*/
|
||||
int (*xce_vote_request_hook)(endpoint_t *ep, xce_request_t *req) = NULL;
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/*
|
||||
* on_vote_connected: iomgr callback fired after TLS handshake completes on an
|
||||
* outgoing vote endpoint.
|
||||
*
|
||||
* Steps:
|
||||
* 1. Cast ep->data to vote_ep_ctx_t.
|
||||
* 2. Build request_vote_req_t from ctx fields.
|
||||
* 3. Build xce_request_t{opcode=OPCode_requestVote, body.requestvotemsg=&vrq}.
|
||||
* 4. Call xce_vote_request_hook (test seam, XCE_TESTING only) — then
|
||||
* call xce_request() to queue the packet on the iomgr send path.
|
||||
* 5. Set ep->msg_handler = vote_reply_msg_handler so the next inbound
|
||||
* packet is decoded as a RequestVoteReply.
|
||||
*
|
||||
* Req 2.3: send vote after TLS handshake.
|
||||
* Req 1.2: vote RPC via iomgr path (xce_request enqueues to endpoint_enq).
|
||||
*/
|
||||
static void on_vote_connected(endpoint_t *ep)
|
||||
{
|
||||
vote_ep_ctx_t *vctx = (vote_ep_ctx_t *)ep->data;
|
||||
|
||||
/* Build the RequestVoteRequest payload */
|
||||
request_vote_req_t vrq;
|
||||
memset(&vrq, 0, sizeof(vrq));
|
||||
vrq.term = vctx->my_term;
|
||||
vrq.candidate_id = (char *)(vctx->my_name ? vctx->my_name : "");
|
||||
vrq.config_tag = vctx->my_tag;
|
||||
vrq.last_log_lsn = vctx->my_lsn;
|
||||
|
||||
/* Wrap in xce_request_t */
|
||||
xce_request_t req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.opcode = OPCode_requestVote;
|
||||
req.body_which = Request_body_requestvotemsg;
|
||||
req.body.requestvotemsg = &vrq;
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
if (xce_vote_request_hook != NULL)
|
||||
xce_vote_request_hook(ep, &req);
|
||||
else
|
||||
xce_request(ep, &req);
|
||||
#else
|
||||
xce_request(ep, &req);
|
||||
#endif
|
||||
|
||||
/* Switch to reply-reading mode: next inbound packet → vote_reply_msg_handler */
|
||||
ep->msg_handler = vote_reply_msg_handler;
|
||||
}
|
||||
|
||||
/*
|
||||
* vote_reply_msg_handler: stub — implemented in task 2.3.
|
||||
* Declared here so on_vote_connected can reference it.
|
||||
* Signature matches msg_handler_t = int (*)(void*) from netio.h.
|
||||
*/
|
||||
static int vote_reply_msg_handler(void *ep)
|
||||
{
|
||||
(void)ep;
|
||||
/* Task 2.3 will implement this; stub returns 0 to avoid crash if called */
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef XCE_TESTING
|
||||
/*
|
||||
* vote_ep_ctx_build_for_test: test helper that allocates a vote_ep_ctx_t,
|
||||
* fills it from the supplied parameters, and wires up ep->data and
|
||||
* ep->on_connected so the test can drive on_vote_connected indirectly.
|
||||
*
|
||||
* Returns ep (same pointer passed in) for call-chain convenience.
|
||||
* Caller is responsible for free(ep->data) after use.
|
||||
*/
|
||||
endpoint_t *vote_ep_ctx_build_for_test(endpoint_t *ep,
|
||||
ha_state_t *ha,
|
||||
ha_elect_ctx_t *ctx,
|
||||
int peer_idx,
|
||||
uint64_t my_term,
|
||||
uint64_t my_tag,
|
||||
uint64_t my_lsn,
|
||||
const char *my_name)
|
||||
{
|
||||
vote_ep_ctx_t *vctx = calloc(1, sizeof(*vctx));
|
||||
if (!vctx) return ep; /* test will fail when on_connected is NULL */
|
||||
|
||||
vctx->ha = ha;
|
||||
vctx->c = NULL;
|
||||
vctx->ctx = ctx;
|
||||
vctx->peer_idx = peer_idx;
|
||||
vctx->my_term = my_term;
|
||||
vctx->my_tag = my_tag;
|
||||
vctx->my_lsn = my_lsn;
|
||||
vctx->my_name = my_name;
|
||||
|
||||
ep->data = vctx;
|
||||
ep->on_connected = on_vote_connected;
|
||||
return ep;
|
||||
}
|
||||
|
||||
/*
|
||||
* election_timeout_fn_for_test: test helper that exercises the election timeout
|
||||
* callback logic with an explicit cancelled_val instead of an atomic field.
|
||||
* Allows testing the cancelled-gate without setting up a real delaymgr.
|
||||
*
|
||||
* Returns 0 in all cases (mirrors the real callback's return value).
|
||||
*/
|
||||
int election_timeout_fn_for_test(ha_state_t *ha, ha_elect_ctx_t *ctx,
|
||||
int cancelled_val)
|
||||
{
|
||||
if (cancelled_val)
|
||||
return 0;
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
ha_elect_evaluate_and_signal(ha, ctx);
|
||||
pthread_mutex_unlock(&ctx->lock);
|
||||
return 0;
|
||||
}
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/* ── Per-peer vote-request thread ──────────────────────────────────────────
|
||||
*
|
||||
* vote_thread_ctx_t: arguments passed to each vote_request_thread().
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue