fix(election-iomgr-refactor): Raft election, config decode, and RDHUP chain

- ha.c: self-vote in ha_start_election_async (Raft invariant)
- ha.c: heartbeat_timeout_ms for hb_timeout_thread (not election_timeout_ms)
- ha.c: clear voted_for before RDHUP jitter (stale vote from dead primary)
- ha.c: same-term voted_for check in RDHUP jitter
- config_repl.c/h: use xce_decode for upgradeConfig (capnp extraction before capn_free)
- cluster.c: fix upgradeConfig handler direction (standbys apply, primaries skip)
- cluster.c: remove raw_pkt_copy hack; use req directly
- join.c: push config to newly-joined standbys
- typemap.h/request.h/request.c: raw_sha256/raw_bytes fields for decoded upgradeConfig
- test_ha_failover.c: update heartbeat timeout test to use g_fast_cfg
This commit is contained in:
Rongsong Shen 2026-05-31 03:54:30 +00:00
parent 1ded1022fb
commit dbb750ebd3
8 changed files with 198 additions and 102 deletions

View file

@ -69,6 +69,56 @@ int xce_decode(io_packet_t* pkt, xce_request_t* req)
decode_Request(req, &rq);
/* OPCode_upgradeConfig carries sha256 and bytes as List(UInt8) on the wire.
* decode_UpgradeConfigRequest() intentionally leaves raw_sha256/raw_bytes as
* NULL (the project's Data union cannot carry raw bytes via decode_Data_ptr).
* Extract them here using UpgradeConfigRequest_get_* + capn_getv8 BEFORE
* capn_free() destroys the capnp segment. */
if (rq.opcode == OPCode_upgradeConfig
&& req->body_which == Request_body_upgradeconfigrequestmsg
&& req->body.upgradeconfigrequestmsg != NULL) {
upgrade_config_req_t *ucr = req->body.upgradeconfigrequestmsg;
UpgradeConfigRequest_ptr u_ptr = rq.body.upgradeconfigrequestmsg;
/* sha256 */
{
Data_ptr dp = UpgradeConfigRequest_get_sha256(u_ptr);
capn_list8 sha_list;
sha_list.p = dp.p;
if (sha_list.p.type != CAPN_NULL) {
int slen = capn_len(sha_list);
if (slen > 0) {
uint8_t *sbuf = (uint8_t *)malloc((size_t)slen);
if (sbuf && capn_getv8(sha_list, 0, sbuf, slen) == slen) {
ucr->raw_sha256 = sbuf;
} else {
free(sbuf);
}
}
}
}
/* bytes */
{
Data_ptr dp = UpgradeConfigRequest_get_bytes(u_ptr);
capn_list8 bytes_list;
bytes_list.p = dp.p;
if (bytes_list.p.type != CAPN_NULL) {
int blen = capn_len(bytes_list);
if (blen > 0) {
uint8_t *bbuf = (uint8_t *)malloc((size_t)blen);
if (bbuf && capn_getv8(bytes_list, 0, bbuf, blen) == blen) {
ucr->raw_bytes = bbuf;
ucr->raw_bytes_len = (size_t)blen;
} else {
free(bbuf);
}
}
}
}
}
TRACE("%s request op %d seq %ld from %s", __func__, req->opcode, req->seq,
REQUEST_FROM(req));
capn_free(&c);
@ -85,6 +135,35 @@ void init_reply_by_req(xce_reply_t* reply, xce_request_t* req)
reply->opcode = req->opcode;
}
/*
* xce_free_request: free an xce_request_t and ALL owned sub-fields.
*
* The generated free_Request() handles the capnp-decoded fields but does
* not know about raw_sha256/raw_bytes on upgrade_config_req_t (those are
* extra fields populated by xce_decode() before capn_free()). This wrapper
* frees those extra fields first, then calls free_Request().
*
* Use this everywhere instead of calling free_Request() directly.
*/
void xce_free_request(xce_request_t *req)
{
if (req == NULL)
return;
/* Free raw_* fields that the generated free_UpgradeConfigRequest skips */
if (req->body_which == Request_body_upgradeconfigrequestmsg
&& req->body.upgradeconfigrequestmsg != NULL) {
upgrade_config_req_t *ucr = req->body.upgradeconfigrequestmsg;
free(ucr->raw_sha256);
ucr->raw_sha256 = NULL;
free(ucr->raw_bytes);
ucr->raw_bytes = NULL;
ucr->raw_bytes_len = 0;
}
free_Request(req);
}
int xce_request(endpoint_t* ep, xce_request_t* req)
{
int rc;
@ -567,31 +646,17 @@ cleanup:
}
/*
* xce_upgrade_config_req_extract: attempt to extract sha256 + bytes
* from a decoded OP_upgradeConfig request.
* xce_upgrade_config_req_extract: extract sha256 + bytes from a decoded
* OP_upgradeConfig request.
*
* ARCHITECTURAL FINDING (Task A research):
* --------------------------------------------------------------------
* The decoded form is unusable for byte extraction. When iomgr receives
* an OP_upgradeConfig packet, xce_decode() calls decode_Request →
* decode_UpgradeConfigRequest → decode_Data_ptr. decode_Data_ptr
* allocates a fresh xce_data_t and walks decode_Data, which treats the
* pointer as a discriminated Data union (reading a 16-bit discriminator
* at offset 0). But the wire actually carries a List(UInt8) with
* datasz=0, so capn_read16 returns 0 (Data_data_schedjobs) and a
* zero-filled xce_jobs_data_t is allocated. No raw bytes survive.
* xce_decode() now extracts the raw List(UInt8) bytes from the capnp
* segment before capn_free(), storing malloc'd copies in
* req->body.upgradeconfigrequestmsg->raw_sha256 and ->raw_bytes.
* This function returns shallow copies (pointers into the struct); the
* caller must NOT free them — they are owned by the xce_request_t and
* released by free_Request().
*
* Worse: xce_decode() then calls capn_free() and free_packet(), so the
* original capnp segment containing the List(UInt8) bytes is gone by
* the time any handler runs. The xce_data_t* fields on the decoded
* xce_request_t are non-NULL but contain only the bogus jobs_data_t.
*
* This function therefore always returns -ENOTSUP. Task D must use
* xce_upgrade_config_req_extract_raw() below and arrange for the raw
* io_packet_t bytes to reach handle_upgrade_config() before xce_decode
* destroys them — e.g. by adding a pre-decode peek for OP_upgradeConfig
* in the message-handler dispatch, or by stashing the raw buffer on
* xce_request_t for select opcodes.
* Returns 0 on success, -EINVAL on bad args or missing fields.
*/
int xce_upgrade_config_req_extract(const xce_request_t *req,
uint8_t **out_bytes, size_t *out_len,
@ -604,7 +669,22 @@ int xce_upgrade_config_req_extract(const xce_request_t *req,
*out_bytes = NULL;
*out_len = 0;
*out_sha256 = NULL;
return -ENOTSUP;
if (req->body_which != Request_body_upgradeconfigrequestmsg
|| req->body.upgradeconfigrequestmsg == NULL) {
return -EINVAL;
}
const upgrade_config_req_t *ucr = req->body.upgradeconfigrequestmsg;
if (ucr->raw_sha256 == NULL || ucr->raw_bytes == NULL
|| ucr->raw_bytes_len == 0) {
return -EINVAL;
}
*out_sha256 = ucr->raw_sha256;
*out_bytes = ucr->raw_bytes;
*out_len = ucr->raw_bytes_len;
return 0;
}
/*