forked from lei.chen/myxce
feat(ha-heartbeat-liveness): role-based liveness reset in opcode dispatch (task 3.1)
Replace fd-based guard in OPCode_nop with role==CTRL_STANDBY check. Add ha_on_heartbeat_received call to OPCode_hello ctrlhello path when standby. Satisfies Req 1.3 (any message resets deadline) and Req 4.1 (ctrlhello counts). 3 new dispatch guard tests added to test_ha_liveness.c.
This commit is contained in:
parent
622302a55c
commit
d937c1c1bb
3 changed files with 104 additions and 3 deletions
|
|
@ -39,7 +39,7 @@
|
|||
- _Boundary: ha.c (primary heartbeat path, standby_ep_ctx_t)_
|
||||
|
||||
- [ ] 3. Integration: wire liveness reset into opcode dispatch
|
||||
- [ ] 3.1 Update cluster.c opcode handlers to reset standby liveness on any primary message
|
||||
- [x] 3.1 Update cluster.c opcode handlers to reset standby liveness on any primary message
|
||||
- `OPCode_nop` handler: replace `if (ep->fd == c->ha.primary_ctrl_fd)` guard with `if (atomic_load(&c->ha.role) == CTRL_STANDBY)` — call `ha_on_heartbeat_received(&c->ha)`
|
||||
- `OPCode_hello` handler (ctrlhello path): after the existing `handle_ctrlhello` call, additionally call `ha_on_heartbeat_received(&c->ha)` when role is CTRL_STANDBY (ctrlhello counts as primary activity per Req 4.1)
|
||||
- `OPCode_replEntry` and `OPCode_walCatchupEntry` handlers: add `ha_on_heartbeat_received(&c->ha)` call when role is CTRL_STANDBY
|
||||
|
|
|
|||
|
|
@ -196,6 +196,10 @@ static int myreq_handler(vtask_t* task)
|
|||
if (request.body_which == Request_body_ctrlhellomsg) {
|
||||
/* Extended OP_ctrlHello with CtrlHelloRequest body — state sync (Req 6.5–6.7) */
|
||||
rc = handle_ctrlhello(c, ep, &request);
|
||||
/* Req 4.1: ctrlhello counts as primary activity — reset liveness deadline. */
|
||||
if ((ctrl_role_t)atomic_load_explicit(&c->ha.role, memory_order_acquire)
|
||||
== CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&c->ha);
|
||||
} else if (request.body_which == Request_body_replymsg
|
||||
&& request.body.replymsg
|
||||
&& request.body.replymsg->data_which
|
||||
|
|
@ -222,8 +226,10 @@ static int myreq_handler(vtask_t* task)
|
|||
reply.status = 0;
|
||||
reply.data_which = RequestReply_data_nodata;
|
||||
xce_reply(ep, c->myhost, &reply);
|
||||
/* Reset heartbeat watchdog when nop arrives on the primary channel */
|
||||
if (ep->fd == c->ha.primary_ctrl_fd)
|
||||
/* Req 1.3: any NOP from primary resets liveness deadline on standby.
|
||||
* Role check is safe from any primary-connected ep — no fd comparison needed. */
|
||||
if ((ctrl_role_t)atomic_load_explicit(&c->ha.role, memory_order_acquire)
|
||||
== CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&c->ha);
|
||||
break;
|
||||
case OPCode_join:
|
||||
|
|
|
|||
|
|
@ -484,6 +484,98 @@ static void test_on_standby_connected_sets_last_push_time(void)
|
|||
printf("test_on_standby_connected_sets_last_push_time: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Task 3.1: cluster.c dispatch guard tests ───────────────────────────── */
|
||||
|
||||
/*
|
||||
* Simulates the cluster.c OPCode_nop dispatch guard:
|
||||
* if role == CTRL_STANDBY → ha_on_heartbeat_received()
|
||||
* Tests that the role-based guard (Req 1.3) correctly updates last_activity
|
||||
* only when on a standby, and is a no-op when on a primary.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Test 11: cluster.c nop dispatch guard — CTRL_STANDBY resets last_activity
|
||||
*
|
||||
* Req 1.3, 4.1: any primary message on a standby resets the liveness deadline.
|
||||
* The cluster.c OPCode_nop handler must call ha_on_heartbeat_received when
|
||||
* role == CTRL_STANDBY (not gated on ep->fd == primary_ctrl_fd).
|
||||
*/
|
||||
static void test_cluster_dispatch_nop_resets_activity_when_standby(void)
|
||||
{
|
||||
ha_state_t ha;
|
||||
TEST_ASSERT_EQUAL_INT(0, ha_init(&ha, &g_cfg, 1));
|
||||
ha_do_demote(&ha); /* role = CTRL_STANDBY */
|
||||
|
||||
ha.last_activity = 0;
|
||||
|
||||
/* Simulate what cluster.c OPCode_nop handler now does:
|
||||
* if (role == CTRL_STANDBY) ha_on_heartbeat_received(&ha); */
|
||||
if ((ctrl_role_t)atomic_load_explicit(&ha.role, memory_order_acquire)
|
||||
== CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&ha);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(0, (int)ha.last_activity,
|
||||
"Req 1.3: OPCode_nop on CTRL_STANDBY must reset last_activity");
|
||||
|
||||
ha_fini(&ha);
|
||||
printf("test_cluster_dispatch_nop_resets_activity_when_standby: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Test 12: cluster.c nop dispatch guard — CTRL_PRIMARY does NOT reset last_activity
|
||||
*
|
||||
* Req 1.3: the guard must only call ha_on_heartbeat_received when CTRL_STANDBY.
|
||||
* When role is CTRL_PRIMARY the call must be skipped so the primary does not
|
||||
* accidentally update its own liveness clock via reflected nops.
|
||||
*/
|
||||
static void test_cluster_dispatch_nop_skips_when_primary(void)
|
||||
{
|
||||
ha_state_t ha;
|
||||
TEST_ASSERT_EQUAL_INT(0, ha_init(&ha, &g_cfg, 0));
|
||||
/* Force role to CTRL_PRIMARY */
|
||||
atomic_store_explicit(&ha.role, (int32_t)CTRL_PRIMARY, memory_order_release);
|
||||
ha.last_activity = 0;
|
||||
|
||||
/* Simulate cluster.c OPCode_nop guard: skip when not CTRL_STANDBY */
|
||||
if ((ctrl_role_t)atomic_load_explicit(&ha.role, memory_order_acquire)
|
||||
== CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&ha);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, (int)ha.last_activity,
|
||||
"Req 1.3: OPCode_nop on CTRL_PRIMARY must NOT update last_activity");
|
||||
|
||||
ha_fini(&ha);
|
||||
printf("test_cluster_dispatch_nop_skips_when_primary: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Test 13: cluster.c ctrlhello dispatch — CTRL_STANDBY resets last_activity (Req 4.1)
|
||||
*
|
||||
* Req 4.1: ctrlhello counts as primary activity — the OPCode_hello ctrlhello
|
||||
* path must call ha_on_heartbeat_received when role == CTRL_STANDBY.
|
||||
*/
|
||||
static void test_cluster_dispatch_ctrlhello_resets_activity_when_standby(void)
|
||||
{
|
||||
ha_state_t ha;
|
||||
TEST_ASSERT_EQUAL_INT(0, ha_init(&ha, &g_cfg, 1));
|
||||
ha_do_demote(&ha); /* role = CTRL_STANDBY */
|
||||
|
||||
ha.last_activity = 0;
|
||||
|
||||
/* Simulate cluster.c OPCode_hello ctrlhello path:
|
||||
* rc = handle_ctrlhello(c, ep, &request);
|
||||
* if (role == CTRL_STANDBY) ha_on_heartbeat_received(&ha); */
|
||||
if ((ctrl_role_t)atomic_load_explicit(&ha.role, memory_order_acquire)
|
||||
== CTRL_STANDBY)
|
||||
ha_on_heartbeat_received(&ha);
|
||||
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(0, (int)ha.last_activity,
|
||||
"Req 4.1: ctrlhello on CTRL_STANDBY must reset last_activity");
|
||||
|
||||
ha_fini(&ha);
|
||||
printf("test_cluster_dispatch_ctrlhello_resets_activity_when_standby: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Suite entry point ──────────────────────────────────────────────────── */
|
||||
|
||||
void test_ha_liveness(void)
|
||||
|
|
@ -499,5 +591,8 @@ void test_ha_liveness(void)
|
|||
RUN_TEST(test_primary_hb_sends_nop_when_idle);
|
||||
RUN_TEST(test_primary_hb_skips_nop_when_fresh);
|
||||
RUN_TEST(test_on_standby_connected_sets_last_push_time);
|
||||
RUN_TEST(test_cluster_dispatch_nop_resets_activity_when_standby);
|
||||
RUN_TEST(test_cluster_dispatch_nop_skips_when_primary);
|
||||
RUN_TEST(test_cluster_dispatch_ctrlhello_resets_activity_when_standby);
|
||||
UNITY_END();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue