feat(ha-heartbeat-committed-propagation): add xce_ha_heartbeat_req sender (task 1.2)
Send the HA heartbeat as OP_nop + the existing HeartbeatMsg body carrying
{term, committed_lsn, committed_config_tag} (senderPriority unused). Generic
xce_nop_req untouched; C-side body support pre-existed (no typemap edit). Req 1.1
producer half. Round-trip test proves the 3 fields survive the wire; lib suite
0 failures under ASAN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f4327ce4f6
commit
282a82ff7e
4 changed files with 129 additions and 1 deletions
|
|
@ -1138,6 +1138,37 @@ int xce_nop_req(endpoint_t* ep)
|
|||
return xce_request(ep, &request);
|
||||
}
|
||||
|
||||
/* xce_ha_heartbeat_req: send the HA heartbeat as an OP_nop request carrying a
|
||||
* HeartbeatMsg body with the primary's committed position. Clone of the nop
|
||||
* send path plus a heartbeatmsg body. Non-blocking, no reply awaited (the
|
||||
* standby's StateReport rides the normal nop reply). senderPriority is unused
|
||||
* (0). Generic xce_nop_req stays body-less. */
|
||||
int xce_ha_heartbeat_req(endpoint_t* ep, uint64_t term, uint64_t committed_lsn,
|
||||
uint64_t committed_config_tag)
|
||||
{
|
||||
heartbeat_msg_t hb = {
|
||||
.sender_priority = 0,
|
||||
.term = term,
|
||||
.committed_lsn = committed_lsn,
|
||||
.committed_config_tag = committed_config_tag,
|
||||
};
|
||||
/* clang-format off */
|
||||
xce_request_t request = {
|
||||
.opcode = OPCode_nop,
|
||||
.body_which = Request_body_heartbeatmsg,
|
||||
.body = {
|
||||
.heartbeatmsg = &hb
|
||||
}
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
if (ep == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return xce_request(ep, &request);
|
||||
}
|
||||
|
||||
int xce_hello_req(endpoint_t* ep, char* msg)
|
||||
{
|
||||
/* clang-format off */
|
||||
|
|
|
|||
|
|
@ -16,6 +16,89 @@ static void test_hostctrl_req_null_ep(void)
|
|||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
}
|
||||
|
||||
/* ── HA heartbeat sender (xce_ha_heartbeat_req) ───────────────────────────
|
||||
* The HA heartbeat is an OP_nop request carrying a HeartbeatMsg body. The
|
||||
* sender builds that request and hands it to xce_request(); since unit tests
|
||||
* have no live endpoint, these tests round-trip the same request shape the
|
||||
* sender builds through xce_encode()/xce_decode() and assert the three
|
||||
* committed-position fields survive the wire. A companion guard confirms the
|
||||
* generic xce_nop_req still produces a body-less nop. */
|
||||
|
||||
/* Build the heartbeat request exactly as xce_ha_heartbeat_req does and encode
|
||||
* it into a heap io_packet_t. xce_decode() takes ownership of the packet
|
||||
* (free_packet + FREEUP), so the caller must NOT free the returned pkt — it is
|
||||
* consumed by the subsequent xce_decode(). Mirrors the test_wal_wire pattern. */
|
||||
static io_packet_t *encode_heartbeat(uint64_t term, uint64_t committed_lsn,
|
||||
uint64_t committed_config_tag)
|
||||
{
|
||||
heartbeat_msg_t hb = {
|
||||
.sender_priority = 0,
|
||||
.term = term,
|
||||
.committed_lsn = committed_lsn,
|
||||
.committed_config_tag = committed_config_tag,
|
||||
};
|
||||
xce_request_t request = {
|
||||
.opcode = OPCode_nop,
|
||||
.body_which = Request_body_heartbeatmsg,
|
||||
.body = { .heartbeatmsg = &hb },
|
||||
};
|
||||
|
||||
io_packet_t *pkt = (io_packet_t *)calloc(1, sizeof(*pkt));
|
||||
TEST_ASSERT_NOT_NULL(pkt);
|
||||
/* xce_encode returns 1 on success (mirrors xce_request's encode path). */
|
||||
TEST_ASSERT_EQUAL_INT(1, xce_encode(&request, pkt));
|
||||
return pkt;
|
||||
}
|
||||
|
||||
static void test_ha_heartbeat_req_null_ep(void)
|
||||
{
|
||||
/* Mirror the nop sender's NULL-endpoint guard. */
|
||||
int rc = xce_ha_heartbeat_req(NULL, 1, 1, 1);
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
}
|
||||
|
||||
static void test_ha_heartbeat_req_roundtrip_fields(void)
|
||||
{
|
||||
/* Arrange/Act: encode a heartbeat carrying a committed position, decode. */
|
||||
io_packet_t *pkt = encode_heartbeat(7, 9, 4);
|
||||
|
||||
xce_request_t req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_decode(pkt, &req)); /* consumes/frees pkt */
|
||||
|
||||
/* Assert: OP_nop + heartbeatmsg arm with the three fields intact. */
|
||||
TEST_ASSERT_EQUAL_INT(OPCode_nop, req.opcode);
|
||||
TEST_ASSERT_EQUAL_INT(Request_body_heartbeatmsg, req.body_which);
|
||||
TEST_ASSERT_NOT_NULL(req.body.heartbeatmsg);
|
||||
TEST_ASSERT_EQUAL_UINT64(7, req.body.heartbeatmsg->term);
|
||||
TEST_ASSERT_EQUAL_UINT64(9, req.body.heartbeatmsg->committed_lsn);
|
||||
TEST_ASSERT_EQUAL_UINT64(4, req.body.heartbeatmsg->committed_config_tag);
|
||||
|
||||
xce_free_request(&req);
|
||||
printf("test_ha_heartbeat_req_roundtrip_fields: PASS\n");
|
||||
}
|
||||
|
||||
/* Guard: the generic nop sender still produces a body-less nop request. */
|
||||
static void test_nop_req_remains_bodyless(void)
|
||||
{
|
||||
xce_request_t request
|
||||
= { .opcode = OPCode_nop, .body_which = Request_body_nomsg };
|
||||
|
||||
io_packet_t *pkt = (io_packet_t *)calloc(1, sizeof(*pkt));
|
||||
TEST_ASSERT_NOT_NULL(pkt);
|
||||
TEST_ASSERT_EQUAL_INT(1, xce_encode(&request, pkt));
|
||||
|
||||
xce_request_t req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_decode(pkt, &req)); /* consumes/frees pkt */
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(OPCode_nop, req.opcode);
|
||||
TEST_ASSERT_EQUAL_INT(Request_body_nomsg, req.body_which);
|
||||
|
||||
xce_free_request(&req);
|
||||
printf("test_nop_req_remains_bodyless: PASS\n");
|
||||
}
|
||||
|
||||
static void test_hostctrl_req_null_ctrl(void)
|
||||
{
|
||||
/* non-NULL dummy ep is enough; the NULL ctrl check fires first */
|
||||
|
|
@ -105,4 +188,9 @@ void test_request(void)
|
|||
RUN_TEST(test_hostctrl_req_reject_untag);
|
||||
RUN_TEST(test_hostctrl_req_reject_negative_action);
|
||||
RUN_TEST(test_init_reply_by_req_zeroes_reply);
|
||||
|
||||
printf("\n>> HA heartbeat sender round-trip tests\n\n");
|
||||
RUN_TEST(test_ha_heartbeat_req_null_ep);
|
||||
RUN_TEST(test_ha_heartbeat_req_roundtrip_fields);
|
||||
RUN_TEST(test_nop_req_remains_bodyless);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue